Co powinieneś przetestować za pomocą testów jednostkowych?

122

Jestem świeżo po studiach i zaczynam studia gdzieś w przyszłym tygodniu. Widzieliśmy testy jednostkowe, ale trochę ich nie używaliśmy; i wszyscy o nich mówią, więc pomyślałem, że może powinienem coś zrobić.

Problem polega na tym, że nie wiem, co przetestować. Czy powinienem przetestować wspólny przypadek? Sprawa krawędzi? Skąd mam wiedzieć, że funkcja jest odpowiednio objęta?

Zawsze mam okropne przeczucie, że chociaż test udowodni, że funkcja działa w określonym przypadku, to zupełnie bezużyteczne jest udowodnienie, że funkcja działa, kropka.

skradać się
źródło
Zajrzyj na bloga Roya Osherove . Istnieje wiele informacji na temat testów jednostkowych, w tym filmy. Napisał także książkę „The Art of Unit Testing”, która jest bardzo dobra.
Piers Myers,
9
Zastanawiam się, co o tym sądzisz po prawie 5 latach? Ponieważ coraz bardziej czuję, że ludzie powinni teraz lepiej wiedzieć „czego nie testować jednostkowo”. Rozwój oparty na zachowaniu ewoluował z pytań, które zadałeś.
Remigijus Pankevičius

Odpowiedzi:

121

Moja osobista filozofia była zatem następująca:

  1. Przetestuj typowy przypadek wszystkiego, co możesz. Dzięki temu dowiesz się, kiedy ten kod zepsuje się po wprowadzeniu pewnych zmian (co moim zdaniem jest największą zaletą automatycznego testowania jednostek).
  2. Przetestuj przypadki skrajne kilku niezwykle złożonych kodów, które Twoim zdaniem prawdopodobnie zawierają błędy.
  3. Ilekroć znajdziesz błąd, napisz test, aby go przykryć przed naprawieniem
  4. Dodawaj testy skrajnych przypadków do mniej krytycznego kodu, ilekroć ktoś ma czas na zabicie.
Fishtoaster
źródło
1
Dzięki za to, szamotałem się tutaj z tymi samymi pytaniami co OP.
Stephen
5
+1, chociaż przetestowałbym również przypadki brzegowe funkcji typu biblioteka / narzędzie, aby upewnić się, że masz logiczny interfejs API. np. co się stanie, gdy wartość null zostanie przekroczona? co z pustymi danymi wejściowymi? Pomoże to upewnić się, że projekt jest logiczny i udokumentować zachowanie narożnika.
mikera
7
# 3 wydaje się bardzo solidną odpowiedzią, ponieważ jest to prawdziwy przykład tego, jak test jednostkowy mógł pomóc. Jeśli pękł raz, może się złamać ponownie.
Ryan Griffith,
Po rozpoczęciu odkryłem, że nie jestem zbyt kreatywny w opracowywaniu testów. Używam ich więc jako nr 3 powyżej, co zapewnia spokój ducha, że ​​te błędy nigdy więcej nie zostaną wykryte.
ankush981
Twoja odpowiedź została opisana w tym popularnym medium: hackernoon.com/…
BugHunterUK
67

Spośród mnóstwa odpowiedzi dotychczas nikt nie poruszył podziału na równoważności i analizy wartości granicznych , co jest istotnym czynnikiem w odpowiedzi na pytanie. Wszystkie pozostałe odpowiedzi, choć użyteczne, są jakościowe, ale możliwe jest - i lepiej - być tutaj ilościowe. @fishtoaster zapewnia pewne konkretne wytyczne, wystarczy zajrzeć pod zakres kwantyfikacji testowej, ale podział równoważności i analiza wartości brzegowych pozwalają nam lepiej.

W partycjonowaniu równoważności dzielisz zestaw wszystkich możliwych danych wejściowych na grupy na podstawie oczekiwanych wyników. Wszelkie dane wejściowe z jednej grupy przyniosą równoważne wyniki, dlatego takie grupy nazywane są klasami równoważności . (Uwaga: równoważne wyniki nie oznaczają identycznych wyników).

Jako prosty przykład rozważ program, który powinien przekształcać małe znaki ASCII na wielkie litery. Inne postacie powinny przejść transformację tożsamości, tzn. Pozostać niezmienione. Oto jeden z możliwych podziałów na klasy równoważności:

| # |  Equivalence class    | Input        | Output       | # test cases |
+------------------------------------------------------------------------+
| 1 | Lowercase letter      | a - z        | A - Z        | 26           |
| 2 | Uppercase letter      | A - Z        | A - Z        | 26           |
| 3 | Non-alphabetic chars  | 0-9!@#,/"... | 0-9!@#,/"... | 42           |
| 4 | Non-printable chars   | ^C,^S,TAB... | ^C,^S,TAB... | 34           |

Ostatnia kolumna podaje liczbę przypadków testowych, jeśli wyliczysz je wszystkie. Technicznie, zgodnie z regułą @ fishtoaster 1, uwzględniałbyś 52 przypadki testowe - wszystkie te dla dwóch pierwszych wierszy podanych powyżej należą do „wspólnego przypadku”. Reguła 2 @ fishtoaster dodałaby także część lub całość z wierszy 3 i 4 powyżej. Ale przy testowaniu partycjonowania równoważności wystarczy jeden przypadek testowy w każdej klasie równoważności. Jeśli wybierzesz „a”, „g” lub „w”, testujesz tę samą ścieżkę kodu. Tak więc masz w sumie 4 przypadki testowe zamiast 52+.

Analiza wartości brzegowych zaleca nieznaczne udoskonalenie: zasadniczo sugeruje, że nie każdy element klasy równoważności jest, cóż, równoważny. Oznacza to, że wartości graniczne należy również uważać za warte osobnego przypadku testowego. (Jednym z łatwych uzasadnień tego jest niesławny błąd „jeden po drugim” ). Zatem dla każdej klasy równoważności można mieć 3 wejścia testowe. Patrząc na domenę wejściową powyżej - i z pewną wiedzą na temat wartości ASCII - mogę wymyślić te dane wejściowe dla przypadków testowych:

| # | Input                | # test cases |
| 1 | a, w, z              | 3            |
| 2 | A, E, Z              | 3            |
| 3 | 0, 5, 9, !, @, *, ~  | 7            |
| 4 | nul, esc, space, del | 4            |

(Gdy tylko otrzymasz więcej niż 3 wartości graniczne, które sugerują, że możesz chcieć ponownie przemyśleć swoje pierwotne nakreślenia klas równoważności, ale było to na tyle proste, że nie wróciłem do ich poprawiania.) Zatem analiza wartości granicznych prowadzi nas do 17 przypadków testowych - z dużą pewnością pełnego pokrycia - w porównaniu do 128 przypadków testowych do przeprowadzenia wyczerpujących testów. (Nie wspominając już o tym, że kombinatoryka dyktuje, że wyczerpujące testy są po prostu niemożliwe do zastosowania w rzeczywistych aplikacjach!)

Michael Sorens
źródło
3
+1 Właśnie tak intuicyjnie piszę swoje testy. Teraz mogę nadać mu imię :) Dziękujemy za udostępnienie.
guillaume31
+1 za „odpowiedzi jakościowe są przydatne, ale możliwe jest - i lepiej - być ilościowe”
Jimmy Breck-McKye
Myślę, że to dobra odpowiedź, jeśli dyrektywa brzmi „w jaki sposób mogę uzyskać dobry zasięg moich testów”. Myślę, że warto byłoby znaleźć na tym pragmatyczne podejście - czy celem jest, aby każda gałąź logiki na każdej warstwie została dokładnie przetestowana w ten sposób?
Kieren Johnstone,
18

Prawdopodobnie moja opinia nie jest zbyt popularna. Ale sugeruję, abyś był oszczędny dzięki testom jednostkowym. Jeśli masz zbyt wiele testów jednostkowych, możesz łatwo spędzić połowę czasu lub dłużej na utrzymywaniu testów zamiast na faktycznym kodowaniu.

Proponuję napisać testy na rzeczy, które mają złe przeczucia w jelitach lub rzeczy, które są bardzo ważne i / lub elementarne. Testy jednostkowe IMHO nie zastępują dobrej inżynierii i defensywnego kodowania. Obecnie pracuję nad projektem, który jest mniej lub bardziej bezużyteczny. Jest naprawdę stabilny, ale stanowi problem dla refaktora. W rzeczywistości nikt nie dotknął tego kodu w ciągu jednego roku, a stos oprogramowania, na którym jest oparty, ma 4 lata. Dlaczego? Ponieważ jest zaśmiecony testami jednostkowymi, a ściślej: testami jednostkowymi i automatycznymi testami integracji. (Słyszałeś kiedyś o ogórkach itp.) A oto najlepsza część: to (jeszcze) bezużyteczne oprogramowanie zostało opracowane przez firmę, której pracownicy są pionierami na scenie programistycznej opartej na testach. :RE

Więc moja sugestia to:

  • Zacznij pisać testy po opracowaniu podstawowego szkieletu, w przeciwnym razie refaktoryzacja może być bolesna. Jako programista, który opracowuje dla innych, nigdy nie spełniasz wymagań od samego początku.

  • Upewnij się, że testy jednostkowe można wykonać szybko. Jeśli masz testy integracyjne (takie jak ogórek), możesz je potrwać dłużej. Ale długotrwałe testy nie są zabawne, uwierz mi. (Ludzie zapominają o wszystkich powodach, dla których C ++ stał się mniej popularny ...)

  • Pozostaw to TDD ekspertom TDD.

  • I tak, czasami koncentrujesz się na przypadkach skrajnych, a czasem na typowych przypadkach, w zależności od tego, gdzie oczekujesz nieoczekiwanego. Chociaż jeśli zawsze oczekujesz nieoczekiwanego, powinieneś naprawdę przemyśleć swój tok pracy i dyscyplinę. ;-)

Philip
źródło
2
Czy możesz podać więcej szczegółów na temat tego, dlaczego testy sprawiają, że to oprogramowanie jest kłopotliwe dla refaktoryzacji?
Mike Partridge
6
Duży +1. Posiadanie ścian testów jednostkowych testujących implementację zamiast reguł powoduje, że każda zmiana wymaga 2-3x więcej
TheLQ
9
Podobnie jak źle napisany kod produkcyjny, źle napisane testy jednostkowe są trudne do utrzymania. „Zbyt wiele testów jednostkowych” brzmi jak brak pozostawania SUCHYM; każdy test powinien dotyczyć / udowodnić określoną część systemu.
Allan
1
Każdy test jednostkowy powinien sprawdzić jedną rzecz, więc nie ma zbyt wielu testów jednostkowych, ale brakuje testów. Jeśli twoje testy jednostkowe są złożone, to kolejny problem.
graffic
1
-1: Myślę, że ten post jest źle napisany. Wymieniono wiele rzeczy i nie wiem, jak one się odnoszą. Jeśli odpowiedź brzmi „bądź ekonomiczny”, to jak w ogóle odnosi się twój przykład? Wygląda na to, że Twoja przykładowa sytuacja (choć prawdziwa) ma złe testy jednostkowe. Wyjaśnij, jakie lekcje mam się z tego nauczyć i jak pomaga mi to być ekonomicznym. Poza tym, szczerze mówiąc, po prostu nie wiem, co masz na myśli mówiąc Leave this TDD stuff to the TDD-experts.
Alexander Bird
8

Jeśli najpierw testujesz za pomocą Test Driven Development, wtedy Twój zasięg wzrośnie w zakresie 90% lub wyższym, ponieważ nie dodasz funkcjonalności bez uprzedniego napisania dla niej nieudanego testu jednostkowego.

Jeśli dodajesz testy po fakcie, nie mogę zalecić, abyś dostał kopię Efektywnej pracy ze starszym kodem autorstwa Michaela Feathersa i przyjrzał się niektórym technikom zarówno dodawania testów do kodu, jak i sposobów refaktoryzacji kodu aby był bardziej testowalny.

Paddyslacker
źródło
Jak obliczyć ten procent pokrycia? Co to znaczy i tak pokryć 90% twojego kodu?
zneak
2
@zneak: istnieją narzędzia pokrycia kodu, które obliczą je dla Ciebie. Szybkie google dla „pokrycia kodu” powinno wywołać wiele z nich. Narzędzie śledzi wiersze kodu, które są wykonywane podczas uruchamiania testów, i bazuje na sumach wierszy kodu w zespole (ach), aby uzyskać procent pokrycia.
Steven Evers
-1. Nie odpowiada na pytanie:The problem is, I don't know _what_ to test
Alexander Bird,
6

Jeśli zaczniesz następujący rozwój testów Driven praktyk, będą one rodzaj poprowadzi Cię przez proces i wiedząc, co testować przyjdzie naturalnie. Niektóre miejsca na początek:

Testy są najważniejsze

Nigdy nie pisz kodu przed napisaniem testów. Wyjaśnienie patrz Red-Green-Refactor-Powtórz .

Napisz testy regresji

Ilekroć napotkasz błąd, napisz testcase i upewnij się, że się nie powiedzie . O ile nie możesz odtworzyć błędu za pomocą nieudanej próby, tak naprawdę go nie znalazłeś.

Powtórz refaktor czerwony-zielony-zielony

Czerwony : Zacznij od napisania najbardziej podstawowego testu dla zachowania, które próbujesz wdrożyć. Pomyśl o tym kroku jak o napisaniu przykładowego kodu, który używa klasy lub funkcji, nad którą pracujesz. Upewnij się, że kompiluje / nie ma błędów składniowych i że zawiedzie . To powinno być oczywiste: nie napisałeś żadnego kodu, więc musi się nie powieść, prawda? Ważną rzeczą do nauczenia się tutaj jest to, że dopóki przynajmniej nie zobaczysz, że test się nie powiedzie, nigdy nie możesz być pewien, że jeśli się powiedzie, zrobi to z powodu czegoś, co zrobiłeś z jakiegoś fałszywego powodu.

Zielony : Napisz najprostszy i najgłupszy kod, który tak naprawdę sprawia, że ​​test się powiedzie. Nie próbuj być mądry. Nawet jeśli widzisz, że istnieje oczywisty przypadek krawędzi, ale test bierze pod uwagę, nie pisz kodu, aby go obsłużyć (ale nie zapomnij o przypadku krawędzi: będziesz go potrzebować później). Chodzi o to, że każdy kawałek kodu yo pisać, każdy if, każdy try: ... except: ...powinien być uzasadniony przypadek testowy. Kod nie musi być elegancki, szybki ani zoptymalizowany. Po prostu chcesz, aby test się zdał.

Refaktoryzacja : Wyczyść kod, popraw nazwy metod. Sprawdź, czy test nadal mija. Być optymistą. Uruchom test ponownie.

Powtórz : Pamiętasz przypadek na krawędzi, którego test nie obejmował, prawda? A więc teraz jest wielki moment. Napisz skrzynkę testową, która obejmie tę sytuację, zobacz, jak się nie udaje, napisz kod, zobacz, jak to się skończy, refaktoryzuj.

Przetestuj swój kod

Pracujesz nad jakimś konkretnym fragmentem kodu i właśnie to chcesz przetestować. Oznacza to, że nie powinieneś testować funkcji biblioteki, biblioteki standardowej ani kompilatora. Staraj się także unikać testowania „świata”. Obejmuje to: wywoływanie zewnętrznych interfejsów API sieci Web, niektóre czynności wymagające intensywnej pracy z bazami danych itp. Ilekroć możesz spróbować wyszydzić go (stwórz obiekt zgodny z tym samym interfejsem, ale zwróci statyczne, predefiniowane dane).

Ryszard Szopa
źródło
1
Zakładając, że mam już istniejącą i (o ile widzę) działającą bazę kodu, co mam zrobić?
zneak 13.10.10
Może to być nieco trudniejsze (w zależności od sposobu pisania kodu). Zacznij od testów regresyjnych (zawsze mają sens), a następnie możesz spróbować napisać testy jednostkowe, aby udowodnić sobie, że rozumiesz, co robi kod. Łatwo jest być przytłoczonym ilością pracy, która jest (pozornie) do wykonania, ale: niektóre testy są zawsze lepsze niż żadne testy.
Ryszard Szopa
3
-1 Nie sądzę, że to bardzo dobra odpowiedź na to pytanie . Pytanie nie dotyczy TDD, pyta o to, co sprawdzić podczas pisania testów jednostkowych. Myślę, że dobra odpowiedź na rzeczywiste pytanie powinna mieć zastosowanie do metodologii innej niż TDD.
Bryan Oakley,
1
Jeśli go dotkniesz, przetestuj. A Clean Code (Robert C Martin) sugeruje napisanie „testów uczenia się” dla kodu innej firmy. W ten sposób uczysz się go używać i masz testy na wypadek, gdyby nowa wersja zmieniła zachowanie, którego używasz.
Roger Willcocks
3

W przypadku testów jednostkowych zacznij od przetestowania, czy robi to, do czego został przeznaczony. To powinien być pierwszy przypadek, który napiszesz. Jeśli częścią projektu jest „powinno się wprowadzić wyjątek, jeśli zdasz śmieci”, przetestuj to również, ponieważ jest to część projektu.

Zacznij od tego. Gdy zdobędziesz doświadczenie w wykonywaniu tego najbardziej podstawowego testowania, zaczniesz się uczyć, czy to wystarcza, i zobaczysz inne aspekty kodu, które wymagają testowania.

Bryan Oakley
źródło
0

Podstawową odpowiedzią jest „przetestować wszystko, co może się zepsuć” .

Co jest zbyt łatwe do złamania? Pola danych, niedoświadczone akcesoria do nieruchomości i podobne górne płyty. Wszystko inne prawdopodobnie implementuje pewną możliwą do zidentyfikowania część wymagania i może skorzystać z testowania.

Oczywiście Twój przebieg i praktyki w twoim środowisku pracy mogą się różnić.

Jeffrey Hantin
źródło
W porządku. Które przypadki powinienem przetestować? „Normalna” sprawa? Sprawa krawędzi?
zneak
3
Praktyczna zasada? Jeden lub dwa w samym środku złotej ścieżki, i tylko wewnątrz i tuż za wszelkimi krawędziami.
Jeffrey Hantin
@JeffreyHantin To jest „analiza wartości granicznej” w innej odpowiedzi.
Roger Willcocks