Zapoznając się z tematyką testów jednostkowych napotykamy podział mocków na trzy grupy: Dynamic Mock, Strict Mock oraz Stub. Dziś pora na poruszenie tego tematu wraz z prezentacją jak je wykorzystać w Rhino Mocks.
Stub
Stub jest niczym innym jak głupią i najprostszą implementacją zadanego interfejsu. Nie możemy go w żaden sposób przetestować, nie możemy sprawdzić jakie metody zostały na nim wywołane. Stub ma jednak jedną cechę, która czyni go niezastąpionym elementem testów jednostkowych: możemy dowolnie konfigurować jego zachowanie. Stuby służą do tworzenia środowiska dla danego testu jednostkowego. Po zdefiniowaniu wartości zwracanych przez ich metody dla żądanych parametrów przekazujemy je jako zależności testowanego obiektu i obserwujemy wyniki.
Dla przykładu rzućmy okiem na dwa poniższe testy:
W pierwszym zakładamy, że logowanie się nie powiedzie i oczekujemy, iż kontroler odpowiednio oznaczy swój ModelState. W teście drugim – wręcz przeciwnie. W tych testach nie interesuje nas co się w metodzie dzieje. Chcemy mieć jedynie pewność, że wynik metody dokonującej uwierzytelnienia w serwisie IAuthenticationService ma wpływ na stan obiektu ModelState – nic mniej, nic więcej.
Zaznaczyć również trzeba, że nieskonfigurowane metody zwrócą domyślną wartość dla danego typu, a więc null, false lub 0.
Małe wtrącenie: pamiętać należy, że kod testów jednostkowych jest równoprawnym kodem naszej aplikacji i tak jawne łamanie zasady DRY jak powyżej nie powinno mieć miejsca. Doszedłem jednak do wniosku, że dla celów demonstracyjnych tak będzie lepiej.
Dynamic Mock
Zachowanie dynamicznych mocków jest bardzo podobne do zachowania Stubów. Również możemy konfigurować ich zachowanie, definiować zwracane wartości, uzależniać je od przekazywanych parametrów itd. Jedyna różnica pomiędzy nimi jest taka, że rola stuba na tym sie kończy, a dynamicznego mocka dopiero zaczyna. Jak widzieliśmy bowiem we wcześniejszych postach, po wywołaniu testowanych akcji na testowanym obiekcie mamy możliwość skontrolowania co działo się z naszym mockiem:
Interesuje nas jedno: że parametry niezbędne do uwierzytelnienia zostały przekazane dalej. Ani efekt tej operacji, ani inna integracja z tą usługą nie obchodzą nas w najmniejszym stopniu.
Strict Mock
Jak sama nazwa wskazuje, tutaj obowiązuje nas jakaś dyscyplina. Na czym ona polega? Otóż do zachowania dynamicznych mocków dołożona jest jeszcze jedna cecha: jakiekolwiek wykorzystanie takiego mocka bez uprzedniego zdefiniowania owego wykorzystania skończy się wyjątkiem. O ile w przypadku poprzednim sprawdzaliśmy tylko jedną rzecz: że zostanie wywołana metoda Authenticate z parametrami userName i password, o tyle strict mock dokłada jeszcze jedno założenie: oprócz tej metody z tymi parametrami nie zostanie wywołane na tym obiekcie NIC INNEGO. Mały przykład:
Podczas wykonania tego testu otrzymamy wyjątek mówiący o nieuprawnionym wywołaniu metody Authenticate z parametrami “abc” i “def”. Dobrze to czy źle… Można trochę więcej powiedzieć o tym zachowaniu, ale takie samo podsumowanie należy się każdemu wymienionemu typowi obiektów, dlatego przejdźmy do podsumowującej, krótkiej sekcji…
Best practices
Kilka zasad, którymi można (nie jestem żadnym megaguru i zapewne jeśli te zasady olejesz, nic ci się specjalnie nie stanie) się kierować pisząc testy jednostkowe z wykorzystaniem mocków:
- sprawdzamy jedno konkretne zachowanie jednego dynamicznego mocka w jednym teście jednostkowym – żeby test był naprawdę jednostkowy, powinien testować jedną rzecz; najlepiej, jeśli jest to po prostu wywołanie jednej metody czy zweryfikowanie jednej wartości
- nie używamy strick mocków – testy jednostkowe są od tego, aby zweryfikować że jakaś akcja ZACHODZI, a nie że nic poza oczekiwanymi akcjami NIE ZACHODZI; w przeciwnym wypadku testy jednostkowe polegają po prostu na przepisaniu normalnego kodu w kolejne miejsce; wyobraźmy sobie, że mamy przetestować metodę robiącą sporo więcej niż tylko przekazanie gdzieś parametrów jak miało to miejsce w przykładzie… wówczas musielibyśmy definiować za każdym razem wszystkie wywoływane metody na wszystkich przekazanych zależnościach tylko po to, aby przetestować tą jedną która nas interesuje… a nie na tym to powinno polegać; dodatkowo już samo spojrzenie na wygląd składni do zabawy z takimi mockami powinien nas nieco zastanowić i odstraszyć
- mały tip: dynamiczne mocki doskonale sprawdzają się w roli stubów; sam osobiście nigdy nie używam stubów z tego prostego względu, że w metodzie Setup przed każdym testem inicjalizuję wszystkie dostępne obiekty jako dynamiczne mocki; do konfiguracji i dostarczania oczekiwanych efektów nadają się równie dobrze jak stuby, a jednocześnie możemy do wszystkiego użyć jednej instancji danego typu bez mieszania mocków ze stubami
- na koniec jeszcze raz podkreślę fakt niemający ścisłego związku z mockami: testy jednostkowe należy pisać z głową, z zachowaniem podstawowych “zasad higieny kodu”, z myślą o ich przyszłym utrzymaniu; ten kod w równym stopniu należy do systemu jak to, co docelowo ma działać u klienta! w przeciwnym wypadku, jeśli zapuścimy się jak stare dziady pod mostem, bardzo szybko odejdzie nam ochota na pisanie testów