Testowanie listy… Wszystko w jednym teście lub jeden test dla każdego warunku?

21

Testuję, czy funkcja działa zgodnie z oczekiwaniami na liście. Więc chcę przetestować

f(null) -> null
f(empty) -> empty
f(list with one element) -> list with one element
f(list with 2+ elements) -> list with the same number of elements, doing what expected

Aby to zrobić, jakie jest najlepsze podejście?

  • Testowanie wszystkich przypadków w tym samym teście (metodzie) pod nazwą „WorksAsExpected”
  • Umieszczenie jednego testu dla każdego przypadku, a tym samym
    • „WorksAsExpectedWhenNull”
    • „WorksAsExpectedWhenEmpty”
    • „WorksAsExpectedWhenSingleElement”
    • „WorksAsExpectedWhenMoreElements”
  • Kolejny wybór, o którym nie myślałem :-)
malarres
źródło
2
Pisałbym je jako osobne przypadki testowe. Możesz użyć testów sparametryzowanych, jeśli twój system testowy to obsługuje.
jonrsharpe
5
Jeśli piszesz testy w danym ... Kiedy ... To styl, to staje się oczywiste, że naprawdę powinny być testowane osobno ...
Robbie Dee
1
Chciałbym tylko dodać: IMO, dobrze jest rozdzielić przypadki brzegowe (takie jak zerowe i puste) na osobne testy, ponieważ często obejmują one specjalną logikę przypadków w różnych możliwych implementacjach, a jeśli testy te zakończą się niepowodzeniem, będą wyraźnie wskazywać w jaki sposób testowany kod zawodzi (nie musisz kopać głębiej ani debugować przypadku testowego, aby dowiedzieć się, co się dzieje).
Filip Milovanović,
1
Lista ze zduplikowanymi elementami?
atayenel

Odpowiedzi:

30

Prostą zasadą, której używam, aby wykonać zestaw testów w jednym przypadku testowym lub w wielu, jest: czy wymaga to tylko jednej konfiguracji?

Więc jeśli testowałem, że dla wielu elementów oba przetworzyły wszystkie z nich i uzyskały poprawny wynik, mogę mieć dwa lub więcej stwierdzeń, ale listę muszę skonfigurować tylko raz. Jeden przypadek testowy jest w porządku.

W twoim przypadku musiałbym jednak ustawić pustą listę, pustą listę itp. To wiele konfiguracji. Zdecydowanie stworzyłbym w tym przypadku wiele testów.

Jak wspomnieli inni, te „wielokrotne testy” mogą istnieć jako pojedynczy sparametryzowany przypadek testowy; tzn. ten sam przypadek testowy jest uruchamiany dla różnych danych konfiguracyjnych. Klucz do zrozumienia, czy jest to realne rozwiązanie, leży w innych częściach testu: „działaniu” i „zapewnieniu”. Jeśli możesz wykonać te same akcje i zapewnienia dla każdego zestawu danych, skorzystaj z tego podejścia. Jeśli zauważysz, że dodajesz ifna przykład różne kody dla różnych części tych danych, nie jest to rozwiązanie. W tym drugim przypadku użyj indywidualnych przypadków testowych.

David Arno
źródło
14

Jest kompromis. Im więcej spakujesz w jednym teście, tym bardziej prawdopodobne będzie, że będziesz mieć efekt cebuli, próbując go przejść. Innymi słowy, pierwsza awaria kończy ten test. Nie poznasz innych twierdzeń, dopóki nie naprawisz pierwszej awarii. To powiedziawszy, posiadanie szeregu testów jednostkowych, które są w większości podobne, z wyjątkiem kodu konfiguracji, jest bardzo pracochłonne, aby dowiedzieć się, że niektóre prace są napisane, a inne nie.

Możliwe narzędzia, w zależności od twojego frameworka:

  • Teorie . Teoria pozwala przetestować szereg faktów na temat zestawu danych. Struktura będzie następnie zasilać twoje testy wieloma scenariuszami danych testowych - albo przez pole, albo za pomocą metody statycznej, która generuje dane. Jeśli niektóre z twoich faktów mają zastosowanie w oparciu o pewne warunki wstępne, a inne nie, te ramy wprowadzają koncepcję założenia . Po Assume.that()prostu pomija test danych, jeśli nie spełni warunku wstępnego. Pozwala to zdefiniować „Działa zgodnie z oczekiwaniami”, a następnie po prostu podać mu dużo danych. Podczas przeglądania wyników masz pozycję dla testów nadrzędnych, a następnie pozycję dla każdego elementu danych.
  • Testy sparametryzowane . Testy sparametryzowane były prekursorem teorii, więc może nie być tego sprawdzania warunków wstępnych, które można mieć z teoriami. Wynik końcowy jest taki sam. Wyświetlane są wyniki, masz pozycję nadrzędną dla samego testu, a następnie określoną pozycję dla każdego punktu danych.
  • Jeden test z wieloma twierdzeniami . Wykonanie konfiguracji zajmuje mniej czasu, ale w końcu od czasu do czasu odkrywasz problemy. Jeśli test jest zbyt długi i testowanych jest zbyt wiele różnych scenariuszy, istnieją dwa duże zagrożenia: jego uruchomienie zajmie dużo czasu, a Twój zespół będzie miał tego dość i wyłączy test.
  • Wiele testów z podobną implementacją . Należy zauważyć, że jeśli twierdzenia są różne, testy nie nakładają się na siebie. Byłaby to jednak konwencjonalna mądrość zespołu skoncentrowanego na TDD.

Nie jestem przekonany, że może być tylko jeden assert w teście stwierdzenie, ale stawiam ograniczenia, że ​​wszyscy twierdzący powinni przetestować warunki pojedynczego działania. Jeśli jedyną różnicą między testami są dane, jestem skłonny do korzystania z bardziej zaawansowanych funkcji testowych opartych na danych, takich jak sparametryzowane testy lub teorie.

Zważyć opcje, aby zdecydować, jaki jest najlepszy wynik. Powiem, że „WorksAsExpectedWhenNull” zasadniczo różni się od wszystkich przypadków, w których mamy do czynienia z kolekcją zawierającą różną liczbę elementów.

Berin Loritsch
źródło
5

Są to różne przypadki testowe, ale kod testu jest taki sam. Dlatego stosowanie sparametryzowanych testów jest najlepszym rozwiązaniem. Jeśli środowisko testowe nie obsługuje parametryzacji, wyodrębnij udostępniony kod do funkcji pomocniczej i wywołaj go z poszczególnych przypadków testowych.

Staraj się unikać parametryzacji za pomocą pętli w jednym przypadku testowym, ponieważ utrudnia to określenie, który zestaw danych spowodował błąd.

W cyklu refaktora TDD czerwony – zielony – powinieneś dodawać jeden przykładowy zestaw danych na raz. Łączenie wielu przypadków testowych w sparametryzowany test byłoby częścią etapu refaktoryzacji.

Raczej odmienne podejście to testowanie nieruchomości . Utworzyłbyś różne (sparametryzowane) testy, które potwierdzą różne właściwości twojej funkcji, bez określania konkretnych danych wejściowych. Np. Właściwością może być: dla wszystkich list xslista ys = f(xs)ma taką samą długość jak xs. Ramy testowe wygenerowałyby wówczas interesujące listy i listy losowe oraz zapewniłyby, że twoje właściwości zachowują je wszystkie. Odchodzi to od ręcznego określania przykładów, ponieważ ręczne wybieranie przykładów może pomijać interesujące przypadki krawędzi.

amon
źródło
Czy „miss” w ostatnim zdaniu nie powinno być „find”?
Robbie Dee,
@RobbieDee Angielski jest dwuznaczny, naprawiony.
amon
3

Posiadanie jednego testu dla każdego przypadku jest właściwe, ponieważ testowanie pojedynczej koncepcji w każdym teście jest dobrą wskazówką, która jest często zalecana.

Zobacz ten post: Czy można mieć wiele twierdzeń w teście pojedynczej jednostki? . Jest tam również istotna i szczegółowa dyskusja:

Moją wytyczną jest zazwyczaj to, że testujesz jeden logiczny POJĘCIE na test. możesz mieć wiele twierdzeń na tym samym obiekcie. zwykle będą to te same testowane koncepcje. Źródło - Roy Osherove

[...]

Testy powinny zakończyć się niepowodzeniem tylko z jednego powodu, ale nie zawsze oznacza to, że powinna istnieć tylko jedna instrukcja Assert. IMHO ważniejsze jest trzymanie się wzorca „aranżuj, działaj, potwierdzaj”.

Kluczem jest to, że masz tylko jedną akcję, a następnie sprawdzasz wyniki tej akcji za pomocą asercji. Ale jest to „Ułóż, Działaj, Potwierdź, Koniec testu”. Jeśli masz ochotę kontynuować testowanie, wykonując kolejną czynność, a później więcej stwierdzeń, uczyń to osobnym testem. Źródło

synek
źródło
0

Moim zdaniem zależy to od warunków testu.

  • Jeśli twój test ma tylko 1 warunek, aby skonfigurować test, ale wiele skutków ubocznych. wielokrotne potwierdzenie jest dopuszczalne.
  • Ale jeśli masz wiele warunków, oznacza to, że masz wiele przypadków testowych, każdy powinien być objęty tylko 1 testem jednostkowym.
HungDL
źródło
brzmi to bardziej jak komentarz, patrz Jak odpowiedzieć
komnata