Niedawno dyskutowałem z przyjaciółmi, która z poniższych 2 metod najlepiej jest usunąć wyniki lub wywołania metod z tej samej klasy z metod z tej samej klasy.
To bardzo uproszczony przykład. W rzeczywistości funkcje są znacznie bardziej złożone.
Przykład:
public class MyClass
{
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
protected int FunctionB()
{
return new Random().Next();
}
}
Aby to przetestować, mamy 2 metody.
Metoda 1: Użyj funkcji i akcji, aby zastąpić funkcjonalność metod. Przykład:
public class MyClass
{
public Func<int> FunctionB { get; set; }
public MyClass()
{
FunctionB = FunctionBImpl;
}
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
protected int FunctionBImpl()
{
return new Random().Next();
}
}
[TestClass]
public class MyClassTests
{
private MyClass _subject;
[TestInitialize]
public void Initialize()
{
_subject = new MyClass();
}
[TestMethod]
public void FunctionA_WhenNumberIsOdd_ReturnsTrue()
{
_subject.FunctionB = () => 1;
var result = _subject.FunctionA();
Assert.IsFalse(result);
}
}
Metoda 2: Uczyń członków wirtualnymi, wyprowadź klasę i skorzystaj z klasy pochodnej Funkcje i akcje zastępujące funkcjonalność Przykład:
public class MyClass
{
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
protected virtual int FunctionB()
{
return new Random().Next();
}
}
public class TestableMyClass
{
public Func<int> FunctionBFunc { get; set; }
public MyClass()
{
FunctionBFunc = base.FunctionB;
}
protected override int FunctionB()
{
return FunctionBFunc();
}
}
[TestClass]
public class MyClassTests
{
private TestableMyClass _subject;
[TestInitialize]
public void Initialize()
{
_subject = new TestableMyClass();
}
[TestMethod]
public void FunctionA_WhenNumberIsOdd_ReturnsTrue()
{
_subject.FunctionBFunc = () => 1;
var result = _subject.FunctionA();
Assert.IsFalse(result);
}
}
Chcę wiedzieć, który jest lepszy, a także DLACZEGO?
Aktualizacja: UWAGA: Funkcja B może być również publiczna
c#
design
unit-testing
tranceru1
źródło
źródło
FunctionA
zwraca bool, ale ustawia tylko zmienną lokalnąx
i niczego nie zwraca.public static
do innej klasy.FunctionB
jest zepsuty przez projekt.new Random().Next()
prawie zawsze się myli. Powinieneś wstrzyknąć instancjęRandom
. (Random
jest także źle zaprojektowaną klasą, która może powodować kilka dodatkowych problemów)Odpowiedzi:
Edytowano po oryginalnej aktualizacji plakatu.
Uwaga: nie jest programistą C # (głównie Java lub Ruby). Moja odpowiedź brzmiałaby: w ogóle bym tego nie testował i nie sądzę, że powinieneś.
Dłuższa wersja to: prywatne / chronione metody nie są częściami API, są to po prostu opcje implementacji, które możesz zdecydować o przejrzeniu, aktualizacji lub całkowitym wyrzuceniu bez żadnego wpływu na zewnątrz.
Podejrzewam, że masz test na FunctionA (), która jest częścią klasy widoczną ze świata zewnętrznego. Powinien być jedynym, który ma umowę na wdrożenie (i którą można przetestować). Twoja prywatna / chroniona metoda nie ma umowy na spełnienie i / lub przetestowanie.
Zobacz pokrewną dyskusję tam: https://stackoverflow.com/questions/105007/should-i-test-private-methods-or-only-public-ones
Po komentarzu , jeśli funkcja B jest publiczna, po prostu przetestuję oba za pomocą testu jednostkowego. Możesz myśleć, że test funkcji A nie jest całkowicie „jednostkowy” (jak wywołuje funkcję B), ale nie martwiłbym się tym: jeśli test funkcji B działa, ale nie test funkcji A, oznacza to wyraźnie, że problem nie leży w subdomena FunctionB, która jest wystarczająco dobra dla mnie jako dyskryminatora.
Jeśli naprawdę chcesz całkowicie rozdzielić dwa testy, użyłbym jakiejś techniki kpiny, aby wyśmiewać FunctionB podczas testowania FunctionA (zazwyczaj zwraca ustaloną znaną poprawną wartość). Brakuje mi wiedzy na temat ekosystemu C #, aby doradzić konkretnej kpiącej bibliotece, ale możesz spojrzeć na to pytanie .
źródło
MyClass
i przesłonięcie metody za pomocą funkcji, którą chcesz zlikwidować. Dobrym pomysłem może być również zaktualizowanie pytania w celu uwzględnieniaFunctionB
go jako publicznego.protected
metody są częścią publicznej powierzchni klasy, chyba że upewnisz się, że nie można zaimplementować swojej klasy w różnych zestawach.Zgadzam się z teorią, że jeśli funkcja jest ważna do przetestowania lub jest ważna do zastąpienia, jest wystarczająco ważna, aby nie być prywatnym szczegółem implementacji testowanej klasy, ale być publicznym szczegółem implementacji innej klasy.
Więc jeśli jestem w scenariuszu, w którym mam
Potem idę do refaktoryzacji.
Teraz mam scenariusz, w którym D () jest niezależnie testowalny i w pełni wymienny.
Jako środek organizacji mój współpracownik może nie żyć na tym samym poziomie przestrzeni nazw. Na przykład, jeśli
A
jest w FooCorp.BLL, mój współpracownik może znajdować się na innej głębokiej warstwie, jak w FooCorp.BLL.Collaborators (lub jakakolwiek inna nazwa jest odpowiednia). Mój współpracownik może być ponadto widoczny tylko w zestawie za pomocąinternal
modyfikatora dostępu, który następnie ujawnię również projektom testującym moje jednostki za pomocąInternalsVisibleTo
atrybutu zestawu. Zaletą jest to, że nadal możesz utrzymywać interfejs API w czystości, jeśli chodzi o osoby dzwoniące, jednocześnie produkując weryfikowalny kod.źródło
Dodając do tego, co zauważa Martin,
Jeśli twoja metoda jest prywatna / chroniona - nie testuj jej. Jest to wewnętrzne dla klasy i nie powinno być dostępne poza klasą.
W obu wymienionych przeze mnie podejściach mam obawy -
Metoda 1 - To faktycznie zmienia zachowanie badanej klasy w teście.
Metoda 2 - W rzeczywistości nie testuje kodu produkcyjnego, zamiast tego testuje inną implementację.
W podanym problemie widzę, że jedyną logiką A jest sprawdzenie, czy wynik funkcji B jest parzysty. Mimo że jest to przykładowe, funkcja B podaje wartość losową, którą trudno przetestować.
Oczekuję realistycznego scenariusza, w którym możemy skonfigurować MyClass w taki sposób, abyśmy wiedzieli, jaka funkcja B zwróci. Wtedy nasz oczekiwany wynik jest znany, możemy wywołać FunctionA i potwierdzić rzeczywisty wynik.
źródło
protected
jest prawie taki sam jakpublic
. Tylkoprivate
iinternal
są szczegóły implementacji.internal protected
, użyć prywatnego pomocnika refleksji lub utworzyć klasę pochodną w projekcie testowym.Ja osobiście używam Method1, tj. Przekształcam wszystkie metody w Action lub Funcs, ponieważ to znacznie poprawiło dla mnie testowalność kodu. Podobnie jak w przypadku każdego rozwiązania, z tym podejściem wiążą się zalety i wady:
Plusy
Cons
Podsumowując, korzystanie z testu Funcs and Actions for Unit jest świetne, jeśli wiesz, że twoje klasy nigdy nie zostaną zastąpione.
Ponadto zwykle nie tworzę właściwości Funcs, ale wstawiam je bezpośrednio jako takie
Mam nadzieję że to pomoże!
źródło
Korzystanie z Mock jest możliwe. Nuget: https://www.nuget.org/packages/moq/
I wierz mi, że jest to dość proste i ma sens.
Mock potrzebuje metody wirtualnej do zastąpienia.
źródło