Testy jednostek podziału według wymagań lub metody

17

Mam metodę, dla której chcę pisać testy jednostkowe. Zamierzam zachować to dość ogólne, ponieważ nie chcę omawiać implementacji metody, tylko jej testowanie. Metoda jest następująca:

public void HandleItem(item a)
{         
     CreateNewItem();
     UpdateStatusOnPreviousItem();
     SetNextRunDate();
}

Więc ta klasa ma jedną metodę publiczną, która następnie wywołuje niektóre prywatne metody do wykonania logiki.

Pisząc test jednostkowy, chcę sprawdzić, czy wszystkie trzy rzeczy zostały wykonane. Ponieważ wszystkie są wywoływane w tym samym przebiegu, pomyślałem, że mogę to zrobić jako jeden test:

public void GivenItem_WhenRun_Thenxxxxx
{
     HandleItem(item);
     // Assert item has been created
     // Assert status has been set on the previous item
     // Assert run date has been set
}

Ale pomyślałem, że mógłbym to również napisać jako trzy osobne testy:

public void GivenItem_WhenRun_ThenItemIsCreated()
{
    HandleItem(item);
}

public void GivenItem_WhenRun_ThenStatusIsUpdatedOnPreviousItem()
{
   HandleItem(item);
}

public void GivenItem_WhenRun_ThenRunDateIsSet()
{
     HandleItem(item);
}

Wydaje mi się więc, że jest to ładniejsze, ponieważ zasadniczo wymienia wymagania, ale wszystkie trzy są ze sobą powiązane i wymagają dokładnie takiej samej pracy, jaką wykonano na testowanej metodzie, więc uruchamiam ten sam kod 3 razy.

Czy istnieje zalecane podejście do tego?

Dzięki

ADringer
źródło

Odpowiedzi:

30

Między obydwoma podejściami istnieje subtelna różnica. W pierwszym przypadku, gdy pierwszy Assertzawiedzie, pozostałe dwa nie są już uruchamiane. W drugim przypadku wszystkie trzy testy są zawsze uruchamiane, nawet jeśli jeden się nie powiedzie. W zależności od charakteru testowanej funkcjonalności może to pasować lub nie pasować do twojego przypadku:

  • jeśli sensowne jest uruchamianie trzech twierdzeń niezależnie od drugiego, ponieważ gdy jeden zawiedzie, pozostałe dwa nadal mogą nie zawieść, wówczas drugie podejście ma tę zaletę, że uzyskuje się pełne wyniki testów dla wszystkich 3 testów w jednym przebiegu. Może to być korzystne, jeśli masz zauważalne czasy kompilacji, ponieważ daje to możliwość naprawienia do 3 błędów na raz przed wykonaniem następnej kompilacji.

  • jeśli jednak niepowodzenie pierwszego testu zawsze będzie oznaczało, że pozostałe dwa również się nie powiodą, prawdopodobnie lepiej jest zastosować pierwsze podejście (ponieważ nie ma większego sensu przeprowadzanie testu, jeśli już wiesz o tym wcześniej) zawieść).

Doktor Brown
źródło
2
+1, dobry punkt. Nie przyszło mi do głowy, że czasy budowy mogą być również wąskim gardłem.
Kilian Foth
1
@KilianFoth: Nie pracujesz wystarczająco często w C ++ :(
Matthieu M.
1
@ MatthieuM .: aby być uczciwym, pytanie jest oznaczone jako „C #”
Doc Brown
10

Krótka odpowiedź: o wiele ważniejsze jest, aby twoje testy obejmowały wszystkie funkcje, niż to, jak to robią.

Dłuższa odpowiedź: jeśli nadal chcesz wybierać spośród tych w dużej mierze równoważnych rozwiązań, możesz zastosować kryteria pomocnicze dla tego, co najlepsze. Na przykład,

  • czytelność: jeśli metoda wykonuje wiele rzeczy, które nie są ściśle powiązane, test łączony może być trudny do zrozumienia. Jednak sama metoda może być również trudna do zrozumienia, więc może powinieneś refaktoryzować metodę zamiast testu!)
  • wydajność: jeśli wykonanie metody zajmuje dużo czasu, może to być słaby powód połączenia wszystkich trzech kontroli w celu zaoszczędzenia czasu
  • wydajność2: jeśli uruchomienie kodu instalacyjnego twojego środowiska zajmuje dużo czasu, może to być również słaby powód do unikania wielu metod testowych. (Jeśli jednak jest to naprawdę problem, prawdopodobnie należy naprawić lub zmienić konfigurację testu - testy regresji tracą wiele ze swojej wartości, jeśli nie można ich uruchomić błyskawicznie).
Kilian Foth
źródło
3

Użyj jednego wywołania metody z wieloma potwierdzeniami. Dlatego:

Podczas testowania HandleItem (a) testujesz, czy metoda doprowadziła element do właściwego stanu. Zamiast „jednego potwierdzenia na test” pomyśl „jedna logiczna koncepcja na test”.

Pytanie: Jeśli CreateNewItem się nie powiedzie, ale pozostałe dwie metody się powiodły, czy to oznacza, że ​​HandleItem zakończyło się pomyślnie? Chyba nie.

Dzięki wielu twierdzeniom (z odpowiednimi wiadomościami) będziesz dokładnie wiedział, co się nie udało. Zazwyczaj metoda jest testowana wiele razy dla wielu danych wejściowych lub stanów wejściowych, aby uniknąć wielu twierdzeń.

IMO, te pytania są zazwyczaj oznaką czegoś innego. To znak, że HandleItem nie jest tak naprawdę czymś, co można „testować jednostkowo”, ponieważ wydaje się, że po prostu deleguje na inne metody. Gdy po prostu sprawdzasz, czy HandleItem poprawnie wywołuje inne metody, staje się bardziej kandydatem do testu integracji (w takim przypadku nadal będziesz mieć 3 potwierdzenia).

Możesz rozważyć upublicznienie pozostałych 3 metod i przetestowanie ich niezależnie. Lub nawet wyodrębnić je do innej klasy.

Kevin
źródło
0

Użyj drugiego podejścia. Przy pierwszym podejściu, jeśli test się nie powiedzie, od razu nie dowiesz się, dlaczego, ponieważ może to być jedna z 3 funkcji, które zawiodły. Przy drugim podejściu od razu dowiesz się, gdzie wystąpił problem. Możesz umieścić zduplikowany kod w funkcji Test Setup.

Eternal21
źródło
-1

IMHO powinieneś przetestować trzy części tej metody osobno, abyś dokładniej wiedział, gdzie coś idzie nie tak, unikając dwukrotnego przejrzenia tej samej części kodu.

SuperUser
źródło
-2

Nie sądzę, aby istniała mocna potrzeba do napisania osobnych metod testowania dla twojego przypadku użycia. Jeśli chcesz uzyskać wyniki ze wszystkich trzech zmiennych, możesz przetestować wszystkie trzy i wydrukować ich awarie, łącząc je w stringai stwierdzając, czy łańcuch jest nadal pusty po zakończeniu testu. Utrzymując je wszystkie w tej samej metodzie, twoje warunki i komunikaty o błędach dokumentują oczekiwany stan końcowy metody w jednym miejscu, zamiast dzielić ją na trzy metody, które mogą później zostać rozdzielone.

Oznacza to, że twój pojedynczy test będzie zawierał więcej kodu, ale jeśli masz tak wiele post-warunków, że jest to problem, prawdopodobnie chcesz zreformować metody testowania, aby przetestować poszczególne metody wewnątrz HandleItem.

Iluzoryczny Brian
źródło