Czemu używacie mockito/innych libek do mocków, zamiast fake objectów?

2

Wiem że mockito i generalnie całe to pojęcia mocków, stubów, spy'ów, etc. jest bardzo potężnie zakorzenione w rozwoju aplikacji, ale tak się zastanawiam...

Jak macie jakiś prosty interfejs:

interface Strategy {
  int doSomething(String string);
}

to czemu w kodzie widać takie cóś:

// given
Strategy strategy = mock(Strategy.class)
when(strategy.doSomething("xd")).thenReturn(2);

// given
bool value = objectUnderTest.perform(strategy, "XD");

zamiast zrobić fake'ową implementację

// when
bool value = objectUnderTest.perform(new FakeStrategy(2), "XD");
1

nie wiem skąd taki wniosek - u mnie używamy mocków np. w przypadku, gdy interfejs ma wiele metod a potrzebna jest tylko jedna albo dwie, ale pod warunkiem, że nie ma tam żadnej skomplikowanej logiki. Praktycznie cała reszta została zastąpiona fejkowymi klasami.

2

Mimo całej mojej niechęci do bibliotek do mockowania to pomijanie ich największego feature'a, jakim jest możliwość weryfikacji wywołań, jest nierzetelne. Oczywiście da się to zrobić własnymi klasami, ale to niepotrzebne rzeźbienie czegoś, co już zostało napisane.

4

Ja już dawno przestałem używać.

Większość rzeczy da się przetestować po efektach, a dla tych których się nie da np. sprawdzanie tytułu wysłanej wiadomości email przez FakeEmailSender zawsze można dopisać fakeEmailSender.getSendMessages() i dalej już to zwykłą asercją obrobić lub nawet fakeEmailSender.assertEmailWithTitleSent("foo") lub poprzez assertion object.

Pisałem o tym 3 lata temu: http://blog.marcinchwedczuk.p[...]ithout-your-mocking-framework w sumie nie chce mi się już mordy otwierać jak nadal słyszę takie pytania...

4

Ja również jestem zwolennikiem używania fake'ów, ale to dla tego że sprawa jest głębsza niż tylko kweststia fake vs jakaś bilbioteka mockowania. Ja promuję testowanie w którym skupiamy się na zachowaniu (ang behaviour) procesu, a nie jego szczegółach implementacyjnych. Co za tym idzie, w większości przypadków testowanie tego czy jakaś metoda była wywołana (argument podany wyżej) jest znakiem że coś robimy nie tak, a nie czymś porządanym. W testach skupiam się na stosunkowo prostej zasadzie że dla konretnych danych wejściowych należy sprawdzać konkretne dane wyjściowe, a szczegóły implementacyjne takie jak to czy dana metoda została wywołana nie mają znaczenia. Więc bardziej tutaj chodzi o całą filozofię podejścia do tego co chcemy osiągnąć testami, niż technikalia.

Warto jednak zaznaczyć że ja ogólnie nie jestem zwolennikiem stosowania testów jednostkowych jako podstawowej metody testowania. W mojej pracy (jak i prywatnie) stosujemy podejście hybrydowe, gdzie większość testów pokrywa konkretny workflow, wliczając w to wysyłane w pamięci requesty HTTP, a cała reszta jest fake'owana (mocki stosujemy w wyjątkowych sytuacjach). Takie testy nazywamy behaviour tests ponieważ jak wcześniej wspomniałem testujemy zachowanie konkretnego procesu, nacisk kładąc na dane wejściowe i wyjściowe, a nie to co się dzieje pomiędzy. Ten rodzaj testów to u nas większość, testy jednostkowe są w mniejszości tam gdzie faktycznie ma sens testować konkretną, wyrwaną z kontekstu (to jeden z głównych powodów dla którego preferujemy testy behaviour, bo nie traci się kontekstu) jednostkę kodu- parsery, fabryki, funkcje mapujące itp.

8

pomijanie ich największego feature'a, jakim jest możliwość weryfikacji wywołań, jest nierzetelne.

Tylko, że w 99% (liczba z tyłka) weryfikowanie wywołań jest niedorzeczne.

Co kogo obczodzi czy funkcja dudaJasiuKarazula była wywołana? Jeśli testowana funkcja zwraca prawidłowy rezultat bez wywoływania jakiejś tam funkcji to jeszcze lepiej... Przy refaktoringach, bywa, że zmieniliśmy implementacje, wprowadziliśmy cache, nie zmieniając logik. Funkcja dudaJasiuKarazula jest wywoływana raz, a nie trzy razy jak w oryginalne... i dlatego mam failowac test?

Wyjątek - ten procent to jakieś serwisy/metody void, których nie da się tanio zamockować np. na poziomie tcpip. Coś w stylu "Mailer.sendMail" - czasem można odpuścić i nie stawiać tego smtp :-)
tylko po prostu sprawdzić jak serwis jest wołany. Przeważnie tak się robi (mockuje) jak są wołane jakies "enterprise" serwisy z porytym security.

Ogólnie, prawie nigdy nie używam Mockito sam z siebie. Jak pracuję z różnymi zespołami i rożnym kodem to mam o wiele wiecej zaufania do projektów opartych o ręcznie robione stuby itp. Może mają one mniejsze raportowane pokrycie (np. 70%) w stosunku do typowego projektu z mockito (90%), ale przynajmnie te testy czasem nawet wywalają się jak coś zrypie w logice, a nie tylko jak zmienie niesistotną kolejność wywoływania serwisów. Żeby było śmieszniej to testy oparte o mockito dość często w różnych projektach przepuszczały duże zwały (bo wynik też był mockowany...). (Miałem taki jeden projekt ekstremum, który zrobił ze mnie wściekle nienawidzącego mockito).

Btw. @TomRiddle w Twoim przykładzie to nawet zamiast FakeStrategy użyłbym ad hoc napisanej lambdy. Robię to dość często: - w końcu dobry interfejs ma dokładnie jedna metodę. A interfejs z jedną metodą jest równoważny funkcji.

4
TomRiddle napisał(a):

zamiast zrobić fake'ową implementację

Bo fejkową implementację trzeba napisać, a biblioteka jest darmowa.
Tylko ja raczej staram się nie tworzyć testy w taki sposób, żeby wszystko co potrzebne metodzie wpadało w jej argumentach wywołania, wtedy nie ma potrzeby robienia żadnego //given w ciele testu i tworzenia mocków, sutów, danych testowych, itp.

Aventus napisał(a):

W testach skupiam się na stosunkowo prostej zasadzie że dla konretnych danych wejściowych należy sprawdzać konkretne dane wyjściowe, a szczegóły implementacyjne takie jak to czy dana metoda została wywołana nie mają znaczenia.

Często nie ma danych wyjściowych, a wywołanie określonej metody jest jedynym efektem działania danej jednostki, a nie szczegółem implementacji.

7
Saalin napisał(a):

Mimo całej mojej niechęci do bibliotek do mockowania to pomijanie ich największego feature'a, jakim jest możliwość weryfikacji wywołań, jest nierzetelne.

Weryfikacja wywołań uwielbia się mścić przy robieniu jakichkolwiek zmian w kodzie testowanym przez weryfikację wywołań.

Raz, że kompletnie nie wiadomo co test miał faktycznie testować - wiesz tylko, że miał 3 razy zawołać zależność, ale nie masz pojęcia czemu :(

Dwa, że tak przetestowany kod jest bardzo odporny na jakiekolwiek refaktoringi - możesz przepisać kod na taki, który działa identycznie, ale wszystkie testy zfailują bo nie zgadzają się wywołania.

3

Mockowanie służy do testowania interakcji, tj. sprawdzania czy testowany kod przestrzega protokołu narzuconego przez inne moduły z nim współpracujące. Np. mockiem możesz sprawdzić, czy klasa, która jest cachem faktycznie uderza do zewnętrznego serwisu tylko raz, a za drugim razem bierze z cache'a. Mock daje co łatwy sposób weryfikacji liczby wywołań czy ich kolejności.

Natomiast zwykle testowanie (stuby, fake'i) skupia się na testowaniu logiki samej klasy, tzn czy efekty jej działania są poprawne. Tu byś sprawdził, czy cache zwraca poprawny obiekt, ale nie wykryłbys czy faktycznie wziął zapamiętany wcześniej czy uderzył uderzył jeszcze raz po to samo.

Oczywiście różnica jest subtelna i w sumie najczęściej w kodzie mam testowanie bez mocków.

0
somekind napisał(a):

Często nie ma danych wyjściowych, a wywołanie określonej metody jest jedynym efektem działania danej jednostki, a nie szczegółem implementacji.

Dla tego napisałem że w większości przypadków. Z tym że moim zdaniem to o czym piszesz to znaczna mniejszość, sytuacje takie jak wspomniał jarek czyli np. wysłanie maila. Ale wiadomo, każdy ma inne doświadczenia.

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0