Jak szczegółowe powinny być testy TDD?

18

Podczas szkolenia TDD opartego na oprogramowaniu medycznym wdrażamy następującą historię: „Gdy użytkownik naciśnie przycisk Zapisz, system powinien dodać pacjenta, dodać urządzenie i dodać rekordy danych urządzenia”.

Ostateczne wdrożenie będzie wyglądać mniej więcej tak:

if (_importDialog.Show() == ImportDialogResult.SaveButtonIsPressed)
{
   AddPatient();
   AddDevice();
   AddDeviceDataRecords();
}

Mamy dwa sposoby na jego wdrożenie:

  1. Wywołano trzy testy, w których każdy weryfikuje jedną metodę (AddPatient, AddDevice, AddDeviceDataRecords)
  2. Wywołano jeden test weryfikujący wszystkie trzy metody

W pierwszym przypadku, jeśli stanie się coś złego, jeśli warunek klauzuli, wszystkie trzy testy zakończą się niepowodzeniem. Ale w drugim przypadku, jeśli test się nie powiedzie, nie jesteśmy pewni, co jest dokładnie nie tak. W jaki sposób wolisz

SiberianGuy
źródło

Odpowiedzi:

8

Ale w drugim przypadku, jeśli test się nie powiedzie, nie jesteśmy pewni, co jest dokładnie nie tak.

Myślę, że w dużej mierze zależy to od tego, jak dobre komunikaty o błędach generuje test. Zasadniczo istnieją różne sposoby sprawdzenia, czy metoda została wywołana; np. jeśli użyjesz fałszywego obiektu, otrzymasz dokładny komunikat o błędzie opisujący, której oczekiwanej metody nie wywołano podczas testu. Jeśli sprawdzisz, czy metoda została wywołana poprzez wykrycie efektów wywołania, to od Ciebie zależy, czy wygenerujesz opisowy komunikat o błędzie.

W praktyce wybór między opcjami 1 i 2 zależy również od sytuacji. Jeśli widzę kod pokazany powyżej w starszym projekcie, wybieram pragmatyczne podejście do przypadku nr 2 tylko po to, aby sprawdzić, czy każda z 3 metod jest poprawnie wywoływana, gdy warunek jest spełniony. Gdybym teraz rozwijał ten fragment kodu, najprawdopodobniej 3 wywołania metod byłyby dodawane jeden po drugim, w oddzielnych punktach czasowych (być może kilka dni lub miesięcy od siebie), więc dodałbym nowy, oddzielny test jednostkowy aby zweryfikować każde połączenie.

Zauważ też, że tak czy inaczej, powinieneś również mieć osobne testy jednostkowe, aby sprawdzić, czy każda z poszczególnych metod robi to, co powinna.

Péter Török
źródło
Czy nie uważasz, że rozsądne jest połączenie tych trzech testów w jeden?
SiberianGuy
@Idsa, może być rozsądną decyzją, chociaż w praktyce rzadko zawracam sobie głowę tego rodzaju refaktoryzacją. Z drugiej strony, pracuję ze starszym kodem, w którym priorytety są różne: skupiamy się na zwiększeniu zasięgu testowego istniejącego kodu i utrzymaniu rosnącej liczby testów jednostkowych.
Péter Török,
30

Granulacja w twoim przykładzie wydaje się być różnicą między testami jednostkowymi i akceptacyjnymi.

Unittest testuje pojedynczą jednostkę funkcjonalności przy możliwie jak najmniejszej liczbie zależności. W twoim przypadku mogą być 4 unittests

  • czy AddPatient dodaje pacjenta (tzn. wywołuje odpowiednie funkcje bazy danych)?
  • czy AddDevice dodaje urządzenie?
  • czy AddDeviceDataRecords dodaje rekordy?
  • czy niezmieniona główna funkcja w twoim przykładzie wywołuje AddPatient, AddDevice i AddDeviceFunctions

Unittests są dla programistów , więc mają pewność, że ich kod jest technicznie poprawny

Testy akceptacyjne powinny przetestować połączoną funkcjonalność z perspektywy użytkownika. Powinny być wzorowane na historiach użytkowników i znajdować się na jak najwyższym poziomie. Nie musisz więc sprawdzać, czy funkcje są wywoływane, ale czy osiągnięto korzyść widoczną dla użytkownika :

gdy użytkownik wprowadza dane, klika ok i ...

  • ... idzie na listę pacjentów, powinien zobaczyć nowego pacjenta o podanym nazwisku
  • ... idzie na listę urządzeń, powinien zobaczyć nowe urządzenie
  • ... przechodzi do szczegółów nowego urządzenia, powinien zobaczyć nowe pliki danych

testy akceptacyjne są dla klientów lub dla lepszej komunikacji z nimi.

Aby odpowiedzieć na pytanie „co wolisz”: co jest dla ciebie większym problemem, błędy i regresja (=> więcej nieuczciwych odpowiedzi) lub zrozumienie i sformalizowanie dużego obrazu (=> więcej testów akceptacyjnych)

keppla
źródło
13

Mamy dwa sposoby na jego wdrożenie:

To nieprawda.

Wywołano trzy testy, w których każdy weryfikuje jedną metodę (AddPatient, AddDevice, AddDeviceDataRecords)

Państwo musi to zrobić, aby mieć pewność, że działa.

Wywołano jeden test weryfikujący wszystkie trzy metody

Musisz to również zrobić, aby mieć pewność, że interfejs API działa.

Klasa - jako całość - musi zostać całkowicie przetestowana. Każda metoda

Możesz zacząć od testu obejmującego wszystkie trzy metody, ale niewiele ci to mówi.

jeśli test się nie powiedzie, nie jesteśmy pewni, co jest dokładnie nie tak.

Poprawny. Dlatego testujesz wszystkie metody.

Państwo musi przetestować interfejs publiczny. Ponieważ ta klasa wykonuje trzy plus jedną czynność (nawet jeśli są one połączone w jedną metodę ze względu na historię użytkownika), musisz przetestować wszystkie cztery rzeczy. Trzy zestawy niskiego poziomu i jeden pakiet.

S.Lott
źródło
2

Piszemy nasze testy jednostkowe pod kątem znaczących zdań funkcjonalności, które wielokrotnie mapują się na metodę (jeśli dobrze napisałeś kod), ale czasem stają się większe, obejmując wiele metod.

Na przykład wyobraź sobie, że dodanie pacjenta do systemu wymaga wywołania niektórych podprogramów (funkcji potomnych):

  1. VerifyPatientQualification
  2. Upewnij się, że istnieje
  3. CheckInsuranceHistory
  4. Upewnij się, że pusty

Możemy również napisać test jednostkowy dla każdej z tych funkcji.

Saeed Neamati
źródło
2

Jedną prostą zasadą, którą stosuję, jest nazwać test, aby dokładnie opisał jego działanie. Jeśli nazwa testu staje się zbyt złożona, oznacza to, że test może robić zbyt wiele. Na przykład nazwanie testu w celu wykonania tego, co zaproponujesz w opcji 2, może wyglądać jak PatientIsAddedDeviceIsAddedAndDeviceDataRecordsWhenSaved, który jest znacznie bardziej złożony niż trzy osobne testy PatientIsAddedWhenSaved, DeviceIsAddedWhenSaved, DataRecordsWhenSaved. Myślę też, że lekcje, które można wyciągnąć z BDD, są dość interesujące, gdzie każdy test jest naprawdę reprezentatywny dla jednego wymagania, które można opisać w języku naturalnym.

jpierson
źródło