Jak napisać „dobre” testy jednostkowe?

61

Wywołany tym wątkiem (ponownie) myślę o tym, by w końcu zastosować testy jednostkowe w moich projektach. Kilka plakatów mówi coś w stylu „Testy są fajne, jeśli są dobre testy”. Moje pytanie teraz: co to są „dobre” testy?

W moich aplikacjach główną częścią często jest pewnego rodzaju analiza numeryczna, w zależności od dużych ilości zaobserwowanych danych, i skutkująca funkcją dopasowania, której można użyć do modelowania tych danych. Szczególnie trudno mi było zbudować testy dla tych metod, ponieważ liczba możliwych danych wejściowych i wyników jest zbyt duża, aby przetestować każdy przypadek, a same metody są często dość długie i nie można ich łatwo zrefaktoryzować bez poświęcania wydajności. Szczególnie interesują mnie „dobre” testy dla tego rodzaju metody.

Jens
źródło
8
Każdy dobry test jednostkowy powinien testować tylko jedną rzecz - jeśli się nie powiedzie, powinieneś dokładnie wiedzieć, co poszło nie tak.
gablin
2
W przypadku dużej ilości danych dobrze jest napisać ogólne testy, które mogą przyjmować pliki danych jako dane wejściowe. Pliki danych powinny zwykle zawierać zarówno dane wejściowe, jak i oczekiwany wynik. Za pomocą ram testowych xunit można generować przypadki testowe w locie - po jednym dla każdej próbki danych.
froderik
2
@gablin „Jeśli to się nie powiedzie, powinieneś dokładnie wiedzieć, co poszło nie tak” sugerowałoby, że testy z wieloma możliwymi przyczynami niepowodzenia są w porządku, o ile możesz określić przyczynę na podstawie wyniku testu ...
user253751
Wydaje się, że nikt nie wspominał, że testy jednostkowe mogą sprawdzić, jak długo trwa operacja. Możesz przefakturować kod, mając na uwadze wydajność, upewniając się, że test jednostkowy powie ci, czy pomyślnie przejdzie, czy nie, na podstawie czasu i wyników.
CJ Dennis

Odpowiedzi:

52

Sztuka testów jednostkowych ma następujące zdanie na temat testów jednostkowych:

Test jednostkowy powinien mieć następujące właściwości:

  • Powinien być zautomatyzowany i powtarzalny.
  • Powinno być łatwe do wdrożenia.
  • Po napisaniu powinien pozostać do wykorzystania w przyszłości.
  • Każdy powinien móc go uruchomić.
  • Powinien działać po naciśnięciu przycisku.
  • Powinien działać szybko.

a następnie dodaje, że powinien być w pełni zautomatyzowany, godny zaufania, czytelny i łatwy w utrzymaniu.

Zdecydowanie polecam przeczytanie tej książki, jeśli jeszcze tego nie zrobiłeś.

Moim zdaniem wszystkie te są bardzo ważne, ale trzy ostatnie (godne zaufania, czytelne i możliwe do utrzymania), zwłaszcza, że ​​jeśli twoje testy mają te trzy właściwości, to twój kod zwykle je również ma.

Andy Lowry
źródło
1
+1 za wyczerpującą listę ukierunkowaną na testy jednostkowe (nie testy integracyjne lub funkcjonalne)
Gary Rowe
1
+1 za link. Interesujący materiał można tam znaleźć.
Joris Meys,
1
„Biegnij szybko” ma duże implikacje. Jest to jeden z powodów, dla których testy jednostkowe powinny być uruchamiane w izolacji, z dala od zewnętrznych zasobów, takich jak baza danych, system plików, usługa internetowa itp. To z kolei prowadzi do próbnych prób.
Michael Wielkanoc
1
kiedy mówi It should run at the push of a button, czy to oznacza, że ​​test jednostkowy nie powinien wymagać działania kontenerów (serwera aplikacji) (dla testowanej jednostki) lub połączenia zasobów (takich jak DB, zewnętrzne usługi sieciowe itp.)? Jestem tylko zdezorientowany, które części aplikacji powinny być testowane jednostkowo, a które nie. Powiedziano mi, że testy jednostkowe nie powinny zależeć od połączenia DB i działających kontenerów, a może zamiast tego mogą być używane makiety.
amfibia
42

Dobry test jednostkowy nie odzwierciedla funkcji, którą testuje.

Jako bardzo uproszczony przykład, rozważ, że masz funkcję, która zwraca średnią z dwóch liczb całkowitych. Najbardziej wszechstronny test wywołałby funkcję i sprawdziłby, czy wynik jest w rzeczywistości średnią. To nie ma żadnego sensu: dublujesz (replikujesz) testowaną funkcjonalność. Jeśli popełniłeś błąd w funkcji głównej, popełnisz ten sam błąd w teście.

Innymi słowy, jeśli replikujesz główną funkcjonalność w teście jednostkowym, jest to prawdopodobnie znak, że marnujesz swój czas.

mojuba
źródło
21
+1 To, co byś zrobił w tym przypadku, to przetestowanie za pomocą zakodowanych argumentów i porównanie ze znaną odpowiedzią.
Michael K
Widziałem już ten zapach.
Paul Butcher
Czy możesz podać przykład dobrego testu jednostkowego dla funkcji, która zwraca średnie?
VLAS
2
@VLAS przetestuj predefiniowane wartości, np. Upewnij się, że avg (1, 3) == 2, a także, co ważniejsze, sprawdź przypadki na krawędziach, takie jak INT_MAX, zera, wartości ujemne itp. Jeśli błąd został znaleziony i naprawiony w funkcji, dodaj kolejny przetestuj, aby upewnić się, że ten błąd nigdy nie zostanie ponownie wprowadzony.
mojuba,
Ciekawy. Jak proponujesz uzyskać prawidłowe odpowiedzi na te dane wejściowe testu i nie popełnić tego samego błędu, co kod poddany testowi?
Timo,
10

Dobre testy jednostkowe to w zasadzie specyfikacja w postaci możliwej do uruchomienia:

  1. opisz zachowanie kodu odpowiadającego przypadkom użycia
  2. obejmują techniczne przypadki narożne (co się stanie, jeśli wynik zerowy zostanie zaliczony) - jeśli nie ma testu dla przypadku narożnego, zachowanie jest niezdefiniowane.
  3. psuje się, jeśli testowany kod zmienia się od specyfikacji

Przekonałem się, że Test-Driven-Development jest bardzo dobrze dostosowany do procedur bibliotecznych, ponieważ zasadniczo piszesz najpierw interfejs API, a następnie rzeczywistą implementację.


źródło
7

w przypadku TDD „dobre” funkcje testowe , których chce klient ; funkcje niekoniecznie odpowiadają funkcjom, a programista nie powinien tworzyć scenariuszy testowych w próżni

w twoim przypadku - zgaduję - „funkcją” jest to, że funkcja dopasowania modeluje dane wejściowe z pewną tolerancją błędu. Ponieważ nie mam pojęcia, co naprawdę robisz, coś wymyślam; miejmy nadzieję, że jest to przeciwwskazane.

Przykładowa historia:

Jako [Pilot X-Wing] chcę [błąd dopasowania nie większy niż 0,0001%], aby [komputer namierzający mógł trafić w otwór wylotowy Gwiazdy Śmierci, gdy porusza się z pełną prędkością przez kanion pudełkowy]

Idź więc porozmawiać z pilotami (i z komputerem docelowym, jeśli czujesz). Najpierw mówisz o tym, co „normalne”, a potem o tym, co nienormalne. Dowiesz się, co naprawdę ma znaczenie w tym scenariuszu, co jest wspólne, co jest mało prawdopodobne i co jest po prostu możliwe.

Powiedzmy, że normalnie będziesz miał półsekundowe okno na siedem kanałów danych telemetrycznych: prędkość, skok, przechylenie, odchylenie, wektor docelowy, rozmiar celu i prędkość celu, i że te wartości będą stałe lub zmieniają się liniowo. Nienormalnie możesz mieć mniej kanałów i / lub wartości mogą się szybko zmieniać. Więc razem wymyślić niektórych badań, takich jak:

//Scenario 1 - can you hit the side of a barn?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is zero
    and all other values are constant,
Then:
    the error coefficient must be zero

//Scenario 2 - can you hit a turtle?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is less than c
    and all other values are constant,
Then:
    the error coefficient must be less than 0.0000000001/ns

...

//Scenario 42 - death blossom
Given:
    all 7 channels with 30% dropout and a 0.05 second sampling window
When:
    speed is zero
    and position is within enemy cluster
    and all targets are stationary
Then:
    the error coefficient must be less than 0.000001/ns for each target

Być może zauważyłeś, że nie ma scenariusza dla konkretnej sytuacji opisanej w historii. Okazuje się, po rozmowie z klientem i innymi interesariuszami, że cel w oryginalnej historii był tylko hipotetycznym przykładem. Prawdziwe testy wyszły z późniejszej dyskusji. To może się zdarzyć. Historia powinna zostać napisana od nowa, ale nie musi tak być [ponieważ jest ona jedynie symbolem zastępczym do rozmowy z klientem].

Steven A. Lowe
źródło
5

Utwórz testy dla przypadków narożnych, takich jak zestaw testowy zawierający tylko minimalną liczbę danych wejściowych (możliwe 1 lub 0) i kilka standardowych przypadków. Te testy jednostkowe nie zastępują dokładnych testów akceptacyjnych, ani nie powinny.

użytkownik 281377
źródło
5

Widziałem wiele przypadków, w których ludzie poświęcają wiele wysiłku na pisanie testów rzadko wprowadzanego kodu, a nie na pisanie testów często wprowadzanego kodu.

Zanim usiądziesz, aby napisać jakieś testy, powinieneś spojrzeć na jakiś wykres połączeń, aby upewnić się, że planujesz odpowiedni zasięg.

Ponadto nie wierzę w pisanie testów tylko po to, aby powiedzieć „Tak, testujemy to”. Jeśli korzystam z biblioteki, która została upuszczona i pozostanie niezmienna, nie będę marnować dnia na pisanie testów, aby upewnić się, że wewnętrzne funkcje interfejsu API, które nigdy się nie zmienią, działają zgodnie z oczekiwaniami, nawet jeśli niektóre części wysoko na wykresie połączeń. Testy, które spożywają wspomnianej biblioteki (mój własny kod) wskazują na to.

Tim Post
źródło
ale co w późniejszym terminie, gdy biblioteka będzie miała nowszą wersję z naprawioną usterką?
@ Thorbjørn Ravn Andersen - To zależy od biblioteki, co się zmieniło i od własnego procesu testowania. Nie zamierzam pisać testów dla kodu, który, jak wiem, działa, kiedy go upuszczam i nigdy nie dotykam. Jeśli więc działa po aktualizacji, to z umysłu idzie :) Oczywiście są wyjątki.
Tim Post
jeśli zależy od biblioteki, najmniej można zrobić, to napisać testy, które pokazują, co można oczekiwać wspomnianej biblioteki faktycznie zrobić ,
... a jeśli to się zmieniło, testy na rzeczy, które konsumują wspomnianą bibliotekę ... Nie muszę testować wewnętrznych elementów kodu innej firmy. Odpowiedź została zaktualizowana w celu zachowania przejrzystości.
Tim Post
4

Nie do końca TDD, ale po przejściu do kontroli jakości możesz poprawić swoje testy, konfigurując przypadki testowe w celu odtworzenia błędów, które pojawią się podczas procesu kontroli jakości. Może to być szczególnie cenne, gdy korzystasz z długoterminowego wsparcia i zaczynasz docierać do miejsca, w którym ryzykujesz, że ludzie przypadkowo przywrócą stare błędy. Przeprowadzenie testu uchwycenia jest szczególnie cenny.

glenatron
źródło
3

Staram się, aby każdy test testował tylko jedną rzecz. Staram się nadawać każdemu testowi nazwę jak shouldDoSomething (). Próbuję przetestować zachowanie, a nie implementację. Testuję tylko metody publiczne.

Zwykle mam jeden lub kilka testów na sukces, a potem może garść testów na porażkę, według metody publicznej.

Często używam makiet. Dobra mock-framework prawdopodobnie byłaby bardzo pomocna, na przykład PowerMock. Chociaż jeszcze nie używam.

Jeśli klasa A używa innej klasy B, dodałbym interfejs X, aby A nie używał B bezpośrednio. Następnie stworzyłbym makietę XMockup i użyłem jej zamiast B w moich testach. Naprawdę pomaga przyspieszyć wykonywanie testów, zmniejsza złożoność testów, a także zmniejsza liczbę testów, które piszę dla A, ponieważ nie muszę radzić sobie ze specyfiką B. Mogę na przykład przetestować, że A wywołuje X.someMethod () zamiast efektu ubocznego wywołania B.someMethod ().

Utrzymuj również kod testowy w czystości.

Podczas korzystania z interfejsu API, takiego jak warstwa bazy danych, wyśmiewałbym go i umożliwiałam makietowi rzucanie wyjątku przy każdej możliwej okazji na polecenie. Następnie uruchamiam jeden test bez rzucania, a następnie w pętli, za każdym razem rzucając wyjątek przy następnej okazji, aż test ponownie się powiedzie. Trochę jak testy pamięci dostępne dla Symbiana.

Roger CS Wernersson
źródło
2

Widzę, że Andry Lowry opublikował już wyniki testów jednostkowych Roya Osherove'a; ale wygląda na to, że nikt nie przedstawił (komplementarnego) zestawu, który wujek Bob podaje w Clean Code (132-133). Używa akronimu FIRST (tutaj z moimi streszczeniami):

  • Szybki (powinni biegać szybko, aby ludzie nie mieli nic przeciwko temu)
  • Niezależny (testy nie powinny się nawzajem konfigurować ani rozbierać)
  • Powtarzalne (powinno działać na wszystkich środowiskach / platformach)
  • Samo sprawdzanie poprawności (w pełni zautomatyzowane; wyjściem powinno być „pozytywny” lub „negatywny”, a nie plik dziennika)
  • Terminowe (kiedy je napisać - tuż przed napisaniem testowanego kodu produkcyjnego)
Kazark
źródło