Jak metoda metody jednostkowej, która zwraca kolekcję, unikając logiki w teście

14

Testuję metodę, która ma wygenerować zbiór obiektów danych. Chcę sprawdzić, czy właściwości obiektów są ustawione poprawnie. Niektóre właściwości zostaną ustawione na to samo; inne zostaną ustawione na wartość zależną od ich pozycji w kolekcji. Naturalnym sposobem na to wydaje się być pętla. Jednak Roy Osherove zdecydowanie odradza stosowanie logiki w testach jednostkowych ( Art of Unit Testing , 178). On mówi:

Test zawierający logikę zwykle testuje więcej niż jedną rzecz na raz, co nie jest zalecane, ponieważ test jest mniej czytelny i bardziej delikatny. Ale logika testowa dodaje również złożoności, która może zawierać ukryty błąd.

Testy powinny być z reguły serią wywołań metod, bez przepływów kontrolnych, nawet try-catchz wywołaniami asertywnymi.

Jednak nie widzę nic złego w moim projekcie (jak inaczej wygenerujesz listę obiektów danych, których niektóre wartości zależą od tego, w jakiej kolejności są? - nie mogą dokładnie wygenerować i przetestować ich osobno). Czy w moim projekcie jest coś niesprzyjającego testom? A może zbyt sztywno poświęca się naukom Osherove? A może jest jakaś tajemna magiczna testowa jednostka, o której nie wiem, która omija ten problem? (Piszę w C # / VS2010 / NUnit, ale jeśli to możliwe, szukam odpowiedzi niezależnych od języka).

Kazark
źródło
4
Polecam nie zapętlać. Jeśli twoim testem jest to, że trzecia rzecz ma ustawiony pasek na Frob, to napisz test, aby dokładnie sprawdzić, czy pasek trzeciej rzeczy jest Frob. To jeden test sam w sobie, przejdź od razu, bez pętli. Jeśli test polega na uzyskaniu kolekcji 5 rzeczy, jest to również jeden test. Nie oznacza to, że nigdy nie masz pętli (wyraźnej lub innej), po prostu nie zawsze musisz. Ponadto traktuj książkę Osherove jako więcej wskazówek niż faktycznych zasad.
Anthony Pegram,
1
@AnthonyPegram Zestawy są nieuporządkowane - Frob może czasami być 3, a czasem 2. Nie można na nim polegać, co powoduje, że pętla (lub funkcja języka taka jak Python in) jest konieczna, jeśli test brzmi: „Frob został pomyślnie dodany do istniejącej kolekcji”.
Izkata
1
@Izbata, jego pytanie wyraźnie wspomina, że ​​zamówienie jest ważne. Jego słowa: „inni otrzymają wartość zależną od ich pozycji w kolekcji”. Istnieje wiele typów kolekcji w C # (język, do którego się odwołuje), które są uporządkowane wstawiania. W tym przypadku możesz również polegać na zamówieniach z listami w Pythonie, o którym wspominasz.
Anthony Pegram,
Powiedzmy, że testujesz metodę resetowania dla kolekcji. Musisz przejrzeć kolekcję i sprawdzić każdy element. W zależności od wielkości kolekcji, nie testowanie jej w pętli jest absurdalne. Lub powiedzmy, że testuję coś, co powinno zwiększyć każdy element w kolekcji. Możesz ustawić wszystkie elementy na tę samą wartość, zadzwoń do swojego przyrostu, a następnie sprawdź. Ten test jest do kitu. Powinieneś ustawić kilka z nich na różne wartości, wywoływać przyrosty i sprawdzać, czy wszystkie różne wartości zwiększały się poprawnie. Sprawdzanie tylko jednego losowego elementu w kolekcji pozostawia wiele przypadkowi.
iheanyi
Nie zamierzam odpowiadać w ten sposób, ponieważ otrzymam miliardy głosów, ale często po prostu toString()kolekcję i porównuję z tym, co powinno być. Prosty i działa.
user949300,

Odpowiedzi:

16

TL; DR:

  • Napisz test
  • Jeśli test zrobi za dużo, kod może też zrobić za dużo.
  • To może nie być test jednostkowy (ale nie zły test).

Pierwszą rzeczą do przetestowania jest nieprzydatność dogmatów. Lubię czytać The Way of Testivus, która wskazuje na pewne problemy z dogmatem w beztroski sposób.

Napisz test, który należy napisać.

Jeśli test musi być napisany w jakiś sposób, napisz go w ten sposób. Próba wymuszenia testu na jakimś wyidealizowanym układzie testu lub jego brak wcale nie jest dobrą rzeczą. Po dzisiejszym teście, który testuje, jest to lepsze niż „idealny” test później.

Wskażę też trochę na brzydkim teście:

Gdy kod jest brzydki, testy mogą być brzydkie.

Nie lubisz pisać brzydkich testów, ale brzydki kod najbardziej wymaga testowania.

Nie pozwól, aby brzydki kod powstrzymał cię przed pisaniem testów, ale pozwól, aby brzydki kod powstrzymał cię przed napisaniem większej ilości.

Można to uznać za truizmy dla tych, którzy śledzą od dawna ... i po prostu zostają zakorzenione w sposobie myślenia i pisania testów. Dla osób, które nie były i próbują dojść do tego punktu, przypomnienia mogą być pomocne (nawet uważam, że ich ponowne czytanie pomaga mi uniknąć uwikłania się w dogmat).


Zastanów się nad tym, pisząc brzydki test, jeśli kod może wskazywać, że kod też próbuje zrobić zbyt wiele. Jeśli testowany kod jest zbyt skomplikowany, aby można go było poprawnie wykonać, pisząc prosty test, warto rozważyć podzielenie go na mniejsze części, które można przetestować za pomocą prostszych testów. Nie należy pisać testu jednostkowego, który robi wszystko (może to nie być test jednostkowy ). Podobnie jak „boskie obiekty” są złe, „boskie testy jednostkowe” również są złe i powinny być wskazówką, aby wrócić i ponownie spojrzeć na kod.

Państwo powinno być w stanie wykonywać cały kod z rozsądnym pokrycia przez takich testów prostych. Testy, które wykonują więcej testów od końca do końca, zajmujące się większymi pytaniami („Mam ten obiekt, przeniesiony do xml, wysłany do usługi sieci Web, zgodnie z regułami, wycofałem się i wycofany”) jest doskonałym testem - ale na pewno nie jest to test jednostkowy (i należy do dziedziny testowania integracji - nawet jeśli ma wyśmiewane usługi, które wywołuje i niestandardowe w bazach pamięci w celu przeprowadzenia testów). Może nadal używać frameworka XUnit do testowania, ale framework testujący nie czyni go testem jednostkowym.


źródło
7

Dodam nową odpowiedź, ponieważ moja perspektywa jest inna niż wtedy, gdy napisałem oryginalne pytanie i odpowiedź; połączenie ich w jedno nie ma sensu.

Powiedziałem w pierwotnym pytaniu

Jednak nie widzę nic złego w moim projekcie (w jaki sposób generujesz listę obiektów danych, których niektóre wartości zależą od tego, w jakiej kolejności są? - nie można ich dokładnie wygenerować i przetestować osobno)

To tam popełniłem błąd. Po zrobieniu programowania funkcjonalnego przez ostatni rok, teraz zdaję sobie sprawę, że potrzebowałem tylko operacji zbierania z akumulatorem. Następnie mógłbym napisać moją funkcję jako funkcję czystą, która działała na jednej rzeczy i użyć jakiejś standardowej funkcji biblioteki, aby zastosować ją do kolekcji.

Tak więc moja nowa odpowiedź brzmi: użyj funkcjonalnych technik programowania , a przez większość czasu unikniesz tego problemu. Możesz pisać swoje funkcje, aby działać na pojedynczych rzeczach i stosować je tylko do kolekcji rzeczy w ostatniej chwili. Ale jeśli są czyste, możesz je przetestować bez odwoływania się do kolekcji.

Aby uzyskać bardziej złożoną logikę, skorzystaj z testów opartych na właściwościach . Gdy mają logikę, powinna być mniejsza i odwrotna do logiki testowanego kodu, a każdy test weryfikuje znacznie więcej niż jednostkowy test oparty na wielkości liter, że niewielka ilość logiki jest tego warta.

Przede wszystkim zawsze polegaj na swoich typach . Zdobądź najsilniejsze typy, jakie możesz, i wykorzystaj je na swoją korzyść. Zmniejszy to liczbę testów, które musisz napisać w pierwszej kolejności.

Kazark
źródło
4

Nie próbuj testować zbyt wielu rzeczy na raz. Każda z właściwości każdego obiektu danych w kolekcji jest za duża na jeden test. Zamiast tego polecam:

  1. Jeśli kolekcja ma stałą długość, napisz test jednostkowy, aby sprawdzić długość. Jeśli ma zmienną długość, napisz kilka testów dla długości, które będą charakteryzowały jego zachowanie (np. 0, 1, 3, 10). Tak czy inaczej, nie sprawdzaj poprawności właściwości w tych testach.
  2. Napisz test jednostkowy, aby sprawdzić poprawność każdej z właściwości. Jeśli kolekcja ma ustaloną długość i jest krótka, po prostu potwierdź jedną właściwość każdego elementu dla każdego testu. Jeśli ma on stałą długość, ale jest długi, wybierz reprezentatywną, ale małą próbkę elementów, które mają zostać potwierdzone dla jednej właściwości. Jeśli ma zmienną długość, wygeneruj stosunkowo krótką, ale reprezentatywną kolekcję (tj. Może trzy elementy) i potwierdź jedną właściwość każdej z nich.

Wykonanie tego w ten sposób sprawia, że ​​testy są na tyle małe, że pomijanie pętli nie wydaje się bolesne. Przykład C # / Unit, podany testowany sposób ICollection<Foo> generateFoos(uint numberOfFoos):

[Test]
void generate_zero_foos_returns_empty_list() { ... }
void generate_one_foo_returns_list_of_one_foo() { ... }
void generate_three_foos_returns_list_of_three_foos() { ... }
void generated_foos_have_sequential_ID()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("ID1", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID2", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID3", foos.Current.id);
}
void generated_foos_have_bar()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
}

Jeśli jesteś przyzwyczajony do paradygmatu „testu płaskich jednostek” (brak zagnieżdżonych struktur / logiki), testy te wydają się dość czyste. W ten sposób unika się logiki w testach, identyfikując pierwotny problem jako próbę przetestowania zbyt wielu właściwości jednocześnie, zamiast braku pętli.

Kazark
źródło
1
Osherove miałby głowę na talerzu za 3 twierdzenia. ;) Pierwsza porażka oznacza, że ​​nigdy nie sprawdzasz pozostałych. Zauważ też, że tak naprawdę nie unikałeś pętli. Właśnie jawnie rozwinąłeś go do postaci wykonanej. Nie jest to ostra krytyka, ale tylko sugestia, aby trochę ćwiczyć izolowanie przypadków testowych do minimalnej możliwej kwoty, aby dać ci bardziej szczegółowe informacje zwrotne, gdy coś zawiedzie, a jednocześnie kontynuować walidację innych przypadków, które mogą prawdopodobnie przejść (lub zawieść, z własne specyficzne informacje zwrotne).
Anthony Pegram,
3
@AnthonyPegram Wiem o paradygmacie „jeden test na test”. Wolę mantrę „przetestuj jedną rzecz” (zgodnie z zaleceniem Boba Martina, przeciw jednemu twierdzeniu na test, w Czystym Kodzie ). Uwaga dodatkowa: frameworki testów jednostkowych, które mają opcję „oczekuj” w porównaniu z „potwierdzeniem”, są dobre (testy Google). Co do reszty, dlaczego nie podzielisz swoich sugestii na pełną odpowiedź z przykładami? Myślę, że mógłbym skorzystać.
Kazark