Kiedy używać Mockito?

0

Hej, Chciałbym się zapytać kiedy używać frameworku Mockito. Dla przykładu weźmy aplikację gdzie mamy trzey kontrolery, trzy serwisy, warstwę jednego repozytorium.

  1. Czy prawidłowe testy jednego serwisu powinny wyglądać w ten sposób, że warstwa repozytorium będzie "zmockowana" oraz zależności do dwóch pozostałych serwisów także "zmockowane"? W jaki sposób powinno się testować taki jeden serwis?

  2. W jaki sposób powinno się testować warstwę kontrolerów? Czy także mockując odpowiedzi? Czy testowanie kontrolerów powinno już bardziej podchodzić pod to co powinna zwracać cała aplikacja? Pytam, ponieważ doświadczyłem w pracy kiedyś aby "mockować" kontrolery i potem sprawdzać je za pomocą MockMvc...

Dzięki za odpowiedź

4
janek_sawicki napisał(a):

Chciałbym się zapytać kiedy używać frameworku Mockito.

Nigdy, prawie nigdy, jak najrzadziej

  1. Czy prawidłowe testy jednego serwisu powinny wyglądać w ten sposób, że warstwa repozytorium będzie "zmockowana" oraz zależności do dwóch pozostałych serwisów także "zmockowane"? W jaki sposób powinno się testować taki jeden serwis?

A czy ten jeden serwis ma w sobie aż tyle logiki że zasługuje na własne testy jednostkowe? Może testy integracyjne wystarczą. Jak nie chcesz stawiać restów przed testami to możliwe że wystarczy kombinacja serwisy + repozytoria + h2

  1. W jaki sposób powinno się testować warstwę kontrolerów?

Kontroler nie zasługuje na własne testy jednostkowe ponieważ zawiera tylko logikę związaną z frameworkiem. Kontroler testuje się przy okazji testów integracyjnych

1
  1. Nigdy :)
  2. Ewentualnie kiedy masz jakąś bardzo złożoną logikę którą koniecznie musisz testować w izolacji

Czy prawidłowe testy jednego serwisu powinny wyglądać w ten sposób, że warstwa repozytorium będzie "zmockowana" oraz zależności do dwóch pozostałych serwisów także "zmockowane"? W jaki sposób powinno się testować taki jeden serwis?

A co robi ten twój serwis konkretnie? Czy jest na tyle złożony, ze test integracyjny polegajacy na stuknięciu w kontroler a potem zweryfikowaniu czy w bazie zapisało się to co miało, nie wystarczy?

W jaki sposób powinno się testować warstwę kontrolerów? Czy także mockując odpowiedzi? Czy testowanie kontrolerów powinno już bardziej podchodzić pod to co powinna zwracać cała aplikacja? Pytam, ponieważ doświadczyłem w pracy kiedyś aby "mockować" kontrolery i potem sprawdzać je za pomocą MockMvc...

xD Tak, najlepiej w ogóle zmockować całą aplikacje i sprawdzać czy mockito zwraca ci oczekiwane odpowiedzi. Przeciez jak zmockujesz kontroler i testujesz tylko mocka :D

Kontrolery nie powinny zawierać logiki, więc nie ma tam czego testować. Testują się "same" w testach integracyjnych, bo każdy taki test zaczyna się od uderzenia klientem w kontroler.

1

Nigdy, prawie nigdy, jak najrzadziej

@KamilAdam rozwiń jak możesz

6
Escanor16 napisał(a):

Nigdy, prawie nigdy, jak najrzadziej

@KamilAdam rozwiń jak możesz

  • Jak nie piszesz testów jednostkowych a tylko integracyjne to nie potrzebujesz mockito.
  • Jak już piszesz testy jednostkowe a serwisy/repozytoria zależą od interfejsu to nie potrzebujesz mockito, bo możesz zrobić fajkowe implementacje na potrzeby testów

Jakby to ode mnie zależało to nigdy bym nie używał mockito, ale zespół mnie zmusza

1

Napisałem kiedyś bloga w tym temacie: https://marcin-chwedczuk.github.io/you-can-live-without-your-mocking-framework (przykłady w C#)
Generalnie zgadzam się z przedmówcami.

2
KamilAdam napisał(a):
  • Jak już piszesz testy jednostkowe a serwisy/repozytoria zależą od interfejsu to nie potrzebujesz mockito, bo możesz zrobić fajkowe implementacje na potrzeby testów

Właśnie o to chodzi, że dzięki Mockito stworzenie takiej fejkowej implementacji na potrzeby testów jest dużo łatwiejsze. Nie musisz wydzielać interfejsów (które będą miały dwie implementacje - testową i prawdziwą).

Nie rozumiem, dlaczego ludzie demonizują narzędzia. Mockito jest wygodne jeśli rozumiesz po co są testy jednostkowe. I to, że każdy widział (albo pisał) testy, które nie robią nic poza testowaniem Mockito i frameworka tego nie zmieni.

4

Mockito dla mnie ma jakiś tam sens jeśli mamy zewnętrzną zależność - jakiś serwis, niekoniecznie rest, dostarczany w postaci klienta java i wszystkie inne sposoby zawiodą lub są pracochłonne.

3

Miałem rok temu sytuacje w której chyba musialem i uzylem mocka. Bylo to mockowanie klasy Socket z wlasnym Selectorem. Mock rzucal wyjatkami - symulujac zrywanie polaczen w roznych momentach. Wszystko inne udaje mi sie testowac podrzucajac implementacje w RAM'ie albo przez testy integracyjne wiec odpowiedzialbym: "prawie nigdy" : D

2

Jak wyżej plus, gdy ogólnie mamy zbyt dużo przypadków do wytestowania, żeby to w sensownym czasie zrobić. Wtedy trzeba gdzieś uciąć i zrobić mocki. Chociaż zwykle zanim projekt osiągnie taki poziom skomplikowania to rozpada się na osobne podprojekty (niekoniecznie mikroserwisy), ale wtedy się mockuje granice aplikacji, więc na to samo wychodzi.

4

To ha dorzuce 3 grosze od siebie - powiem że bawią mnie tezy że Mockito przyśpieszanie pisanie testów i że te testy "jednostkowe" są szybkie. Nie nie są, bo trzeba ogarniac pierdyliard when then, nie zawsze to działa zgodnie z przewidywaniami, znaleźć błąd tez nie jest łatwo a testy w 4/5 składają się z inkwokacji mockosturbacji, niezbyt czytelnych do tego. Do zewnętrznych serwisów preferuje fak'i / Wiremocka.

4

To ja dorzucę jeszcze 2 grosze (żeby piątka była w banku). Widziałem to o czym pisze @scibi92 - sertki powtarzających się when/then w testach.
Po wywaleniu Mockito i zastąpieniu ręcznymi mockami (nadal niezbyt fajnie, ale to był powalony projekt) ilość kodu w testach dramatycznie spadła (człowiek, który ten refaktor robił bardzo się zdziwił, nie to było celem). I w Mockito też się da wydzielić powtarzające się when/ then... ale trudniej na to wpaśc, trudniej tym zarządzać, łatwiej się takim testom rozleźć. Czyli paradoksalne: niby pracy jest mniej, ale jednak więcej.

Btw. celem wywalenia mockito (którego było robiono stopniowo ) było przywrócenie zaufania do testów - to był ten projekt, gdzie pokrycie było na 90%, tony asercji w testach, a spokojnie można było wywalić połowe kodu aplikacji i testy były nadal na zielono. I to wcale nie było fajne. Standardowo: return też był mockowany... z rozpędu. Często nie jest to łatwe do wypatrzenia.

3

A ja dopowiem, że złożone when/then to nie jest tak do końca wina mockito, tylko złego podejścia do testów ;) Można taką samą sieczkę zrobić i z wiremockiem/testami integracyjnymi :)
Dla mnie taki eye-opener to było zaczęcie pisania DSLów do testów, które wszystkie te cuda ukrywają przed kims kto pisze testy. Robisz sobie testConfiguration.withx().withY().withZ()... i cyk, dostajesz jakiś obiekt z którego można sobie powyciągać skonfigurowany stan (np. id usera i takie tam) a jednocześnie system został właśnie odpowiednio skonfigurowany (mocki czekają na requesty, wpisy w bazie są dodane itd). Jasne że gdzieśtam pod spodem trzeba mieć te wszystkie when, ale raz że nie są pokopiowane w 10 miejsc, a dwa, ze pisząc/czytając test w ogóle ich nie widzimy. Mamy takie deklaratywane konfiguracje, bo przecież pisząc test interesuje mnie że w systemie jest user, który np. ma dostęp do pliku X a do pliku Y nie ma. Nie ma znaczenia czy to wynika z jakiegoś wpisu w bazie, z odpowiedzi jakiegoś mikroserwisu, czy z rzutu monetą :)

3
KamilAdam napisał(a):
  • Jak już piszesz testy jednostkowe a serwisy/repozytoria zależą od interfejsu to nie potrzebujesz mockito, bo możesz zrobić fajkowe implementacje na potrzeby testów

No i ja się tu zgadzam z @wartek01 Ludzie krzyczą na Mockito, bo widzieli masę kijowych testów, ale to nie wina biblioteki, tylko klepania testów na pałę. Nie widzę jakiejś specjalnej przewagi w robieniu nowej klasy, do której przekażę zwracane wartości w konstruktorze, a potem w tej klasie będę sprawdzał jakieś cuda albo zwracał obiekty ze słownika — od tego właśnie jest when i thenReturn, nie ma co wymyślać koła na nowo. Tylko trzeba to ukryć w jakiejś metodzie, a nie na pałę kopiować do każdego testu.

Podobnie było z obiektowością i dziedziczeniem, jak DDD i traity/mixiny się upowszechniły w korpojęzykach, to nagle zaczęto dostrzegać zalety, a nie dogmatycznie wyklinać rozwiązanie. Wiem, upraszczam, ale pewnie jak jakaś konfa wymyśli nowy buzzword na czytelne testy z Mockito pod spodem (jakieś Straightforward Stub Solution - SSS®), to wszystko wróci do łask.

Kwestia testowania "integracyjnego" z bazą H2 też już kiedyś komentowałem — dla mnie to ciągle używanie wydmuszki. Inteligentnej i rozbudowanej, to fakt, ale to ciągle wydmuszka a nie ta baza, której używamy na produkcji, więc nie jest to prawdziwy test integracyjny.

4

Kwestia testowania "integracyjnego" z bazą H2 też już kiedyś komentowałem — dla mnie to ciągle używanie wydmuszki. Inteligentnej i rozbudowanej, to fakt, ale to ciągle wydmuszka a nie ta baza, której używamy na produkcji, więc nie jest to prawdziwy test integracyjny.

Dlatego Testcontainers

0

Nie rozumiem oburzenia z waszej strony. Mockito idealnie się nadaje jako tworzenie zależności i fejkowanie ich zachowań. Dla mnie mockito jest wprost stworzone do dependency injection. (ofc testy jednostkowe).
AD 2
Co do testowania integracyjnego na h2... Raz się już na tym naciąłem że niektóre formaty danych na Oracle i H2 znacząco się od siebie różnią, przez co po prostu nie jesteś w stanie tego przetestować...
Może jestem szalony ale mnie się np na H2 fajnie testuje rollbacki :D (przynajmiej w ramach jednego mikorserwisu)

0
KamilAdam napisał(a):
  • Jak już piszesz testy jednostkowe a serwisy/repozytoria zależą od interfejsu to nie potrzebujesz mockito, bo możesz zrobić fajkowe implementacje na potrzeby testów

no właśnie do tego jest mockito, żeby tego ręcznie nie pisać.

1

i w praktyce musisz ręcznie tyle samo mockowań napisać

0

class Service {

    val Repo repo;

    def getSth(id: String): Object {
        repo.findById(id);
    }

    def isSth(parent: Object): Boolean {
        val child: Object = getSth(o.child.id)
        return child.value > parent.value
    }
}

no i proszę skąd bierzesz Repo żeby przetestować isSth bez mockito?

class FakeRepo implements Repo {
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
    // ... perdyliard metod na setki linijek
}

a z mockito tak:

val fakeObj = Object()
mock(Repo).when(any()).then(fakeObj)
4

To tylko łatanie syfa. Problem jest w

    // ... perdyliard metod na setki linijek
0
danek napisał(a):

To tylko łatanie syfa. Problem jest w

    // ... perdyliard metod na setki linijek

nawet jak masz 3 metody to jest wygodniej napisać 2 linijki mocka

1

nawet jak masz 3 metody to jest wygodniej napisać 2 linijki mocka

*2 linijki per test. Wygodniej to jest albo repo w pamięci działające jak prawdziwe albo H2

0

@Julian_ to trochę jak z powermockiem -> jeśli to jest twój kod to kod popraw bo łamiesz jakieś SRP i ISP. Jeśli to nie twój kod i nie masz na niego wpływu to faktycznie może być prosciej walnąć mocki.
Pomysł z robieniem własnych mocków/fakeów odnosi sie do sytuacji, kiedy testów masz bardzo dużo i są do siebie podobne. Jak masz np. 10 czy 20 testów, które wszystkie maja identyczne warunki wstępne i wymagają bardzo podobnego mockowania "na start" to może mieć sens jednak zrobić testową implementacje zamiast powtarzać bardzo podobny kod wszędzie.
Podobnie jeśli setup mocków robi się zbyt skomplikowany i masz np. 30 linijek mockowania i 1 linijkę właściwego testu. Warto wtedy jednak zrobić jakiś sprytny DSL i pewnie też testowe implementacje.

2

@Julian_:
Piszesz jedno fake repo dla jednego prawdziwego repo. Nie trzeba pisać osobnego fake repo per test. Jeśli masz kilkadziesiąt metod w repo to prawdopodobnie masz kilkadziesiąt (albo i więcej) testów z jego użyciem, więc zamiast rozsmarowywać mocki po wszystkich testach piszesz jedno fake repo.

W firmie zrobiłem fake repo, ale niestety koledzy to zepsuli (bo w sumie nie wyjaśniłem dokładnie o co chodzi) :( W każdym razie zamysł jest mniej więcej taki, że:

  • fake repo jest oparte na kolekcjach, więc stworzenie go (w runtime) zajmuje ok 0 milisekund
  • fake repo ma metodki do łatwego wpychania danych do środka i wyciągania ich (nie pokazane w przykładzie poniżej) - skoro mamy kolekcje w środku to można na nich bezpośrednio operować zamiast na metodach repo
  • fake repo testujemy dokładnie tymi samymi testami co real repo (pokazane w przykładzie poniżej) - to jest najważniejsza zaleta (w porównaniu do mockowania), bo dzięki temu infrastruktura testowa też jest gruntownie przetestowana i to praktycznie za darmo
  • w testach logiki biznesowej, (ewentualnie) kontrolerów, etc czyli niedotyczących bezpośrednio bazy używamy samego fake repo, bo jest szybkie (co jest lepsze niż mockowanie, bo fake repo jest przetestowane kompletem testów)
  • przy odrobinie pomysłowości fake repo prawdopodobnie mogłoby się przydać w szybkich i dobrze odizolowanych testach integracyjnych (tych testujących poprawność systemu, a nie wydajność). Nie sprawdziłem tego pomysłu w praktyce.
  • fake repo można by traktować jako cache w przypadku, gdy całkowita ilość danych jest mała. Skoro RealRepo i FakeRepo mają takie same metody to można by odpalać modyfikacje (np addUser) na obu jednocześnie, a wczytywanie danych (np findUser) tylko na FakeRepo. Tego pomysłu też nie sprawdziłem w praktyce.

Przykład w Javie:

Plik SuiteBase.java:

package org.example;

import org.junit.runners.Parameterized;

import java.util.*;

class User {
}

// actually it's broken here, but I didn't want to write complex code
// in reality should access database
class RealRepo {
    Optional<User> findUser(String userName) {
        return Optional.empty();
    }

    void addUser(String userName, User user) {
    }
}

class FakeRepo extends RealRepo {
    Map<String, User> users = new HashMap<>();

    @Override
    Optional<User> findUser(String userName) {
        return Optional.ofNullable(users.get(userName));
    }

    @Override
    void addUser(String userName, User user) {
        if (users.containsKey(userName)) {
            throw new RuntimeException();
        } else {
            users.put(userName, user);
        }
    }
}

public class SuiteBase {
    @Parameterized.Parameters
    public static Collection<Object[]> repo() {
        return Arrays.asList(new Object[][]{
                {new RealRepo()}, {new FakeRepo()}
        });
    }

    protected RealRepo repo;

    public SuiteBase(RealRepo repo) {
        this.repo = repo;
    }
}

Plik RepoTest.java

package org.example;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.util.Optional;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

@RunWith(Parameterized.class)
public class RepoTest extends SuiteBase {
    public RepoTest(RealRepo repo) {
        super(repo);
    }

    @Test
    public void shouldAddUser() {
        assertEquals(Optional.empty(), repo.findUser("bob"));
        repo.addUser("bob", new User());
        assertNotEquals(Optional.empty(), repo.findUser("bob"));
    }
}

Po uruchomieniu RepoTest test shouldAddUser zostanie automatycznie odpalony zarówno dla RealRepo jak i FakeRepo.

PS: kod jest pełen brzydkich wzorców, ale chodzi o pokazanie jak testować FakeRepo i RealRepo jednocześnie, a nie o pokazanie jak ładnie zrobić repo.

Można też wszystko (zarówno mockowanie jak i fake repo) olać i używać do każdego testu H2. Mam wrażenie, że moi koledzy do tego dążą. Mają chyba filozofię typu: skoro mamy kilkanaście mikroserwisów zamiast jednego monolitu to czas budowania pojedynczego mikroserwisu jest wielokrotnie krótszy niż budowanie monolitu, więc można zrobić powolne testy, a i tak mikroserwis będzie się budował krócej niż monolit z szybkimi testami.

0
Wibowit napisał(a):
  • fake repo ma metodki do łatwego wpychania danych do środka i wyciągania ich (nie pokazane w przykładzie poniżej) - skoro mamy kolekcje w środku to można na nich bezpośrednio operować zamiast na metodach repo

I jak często potrzebujesz repo pozwalającego na odczyt i zapis? Ja mam pewnie z 5 razy więcej "repozytoriów", które tylko czytam (czy to baza, czy to inny serwis itp), więc w testach potrzebuję wydmuszki, która dostaje dane przez konstruktor i potem zwraca je przy wywołaniu jakiejś metody do odczytu - nie widzę przewagi nad when.thenReturn z Mockito.

  • fake repo testujemy dokładnie tymi samymi testami co real repo (pokazane w przykładzie poniżej) - to jest najważniejsza zaleta (w porównaniu do mockowania), bo dzięki temu infrastruktura testowa też jest gruntownie przetestowana i to praktycznie za darmo

Z jednej strony spoko, z drugiej strony trochę dziwne jest testowanie testów. Ale jak działa szybko i wymaga jednej linijki kodu, to można się w to bawić.

  • w testach logiki biznesowej, (ewentualnie) kontrolerów, etc czyli niedotyczących bezpośrednio bazy używamy samego fake repo, bo jest szybkie (co jest lepsze niż mockowanie, bo fake repo jest przetestowane kompletem testów)

Jeżeli obawiasz się, że Twój mock zrobiony przy pomocy Mockito jest niepoprawny, to masz coś skopane niezależnie od Mockito.

  • przy odrobinie pomysłowości fake repo prawdopodobnie mogłoby się przydać w szybkich i dobrze odizolowanych testach integracyjnych (tych testujących poprawność systemu, a nie wydajność). Nie sprawdziłem tego pomysłu w praktyce.

Nie bardzo widzę użyteczność tego, ani to prawdziwy load test, ani benchmark. Trudno wyrokować bez konkretnego przykładu użycia.

  • fake repo można by traktować jako cache w przypadku, gdy całkowita ilość danych jest mała. Skoro RealRepo i FakeRepo mają takie same metody to można by odpalać modyfikacje (np addUser) na obu jednocześnie, a wczytywanie danych (np findUser) tylko na FakeRepo. Tego pomysłu też nie sprawdziłem w praktyce.

Raz, że tych danych musiałoby być naprawdę mało (powiedzmy gigabajty maks, żeby maszyna wytrzymała), a dwa, że to i tak nie zadziała w przypadku wielodostępu do bazy danych. Aczkolwiek niektóre frameworki tak keszują i działa, więc pewnie by zadziałało.

Można też wszystko (zarówno mockowanie jak i fake repo) olać i używać do każdego testu H2. Mam wrażenie, że moi koledzy do tego dążą. Mają chyba filozofię typu: skoro mamy kilkanaście mikroserwisów zamiast jednego monolitu to czas budowania pojedynczego mikroserwisu jest wielokrotnie krótszy niż budowanie monolitu, więc można zrobić powolne testy, a i tak mikroserwis będzie się budował krócej niż monolit z szybkimi testami.

To ma i tak tę wadę, że nie jest to ani test jednostkowy (= szybki), ani test integracyjny (= testujący prawdziwe komponenty).

U mnie często testy wyglądają w taki sposób (pseudokod):

IRepository repository;
IService sut;
	
[BeforeEach]
void init(){
	repository = mock();
	sut = new Service(repository);
}


static class TestsForMethod1{
	RepositoryResponse response;
	
	[BeforeEach]
	void init(){
		response = genericResponseWithFullData();
	}

	[Test]
	void test_czegos_prostego(){
		// Tu modyfikujemy response, na przyklad jezeli któres z pól wynikowego JSON-a ma byc nullem, to robimy
		response.property = null;

		runTest();
		
		assert();
	}

	[Test]
	void test_czegos_grubego(){
		// Albo jak potrzebujemy czegos grubszego, to nadpisujemy caly obiekt builderem czy innym DSL-em
		reponse = specialResponse.builder().yada.build();

		runTest();
		
		assert();
	}
	
	void runTest(){
		when(repository.Method()).thenReturn(response);
		sut.doMagic();
	}
}

static class TestsForRepositoryMethod2{
	RepositoryResponse2 response; // Tu odpowiedz do innej metody
	
	[BeforeEach]
	void init(){
		response = genericResponse2WithFullData();
	}

	[Test]
	void test_czegos_tam(){
		response.property = null;

		runTest();
		
		assert();
	}
	
	void runTest(){
		when(repository.Method2()).thenReturn(response);
		sut.doMagic2();
	}
}

I mi się to sprawdza. Jak trzeba mi mądrego mocka, to mogę go łatwo wymienić w jednym miejscu, dodatkowo nie tworzę odpowiedzi w każdym teście, tylko wywołuję jakąś metodę tworzącą cały obiekt i potem nadpisuję jakieś fragmenty (albo używam DSL-a przy grubszych sprawach). Czasami zamiast robić assert w każdym teście, wyciągam oczekiwany wynik testy do pola, nadpisuję w każdym teście, a potem asercje wstawiam do runTest(). W efekcie każdy test jest krótki — najpierw mam nazwę metody mówiącą coś w stylu subtitlesInManyLanguages_firstLanguageSelected, arrange jest krótkie (bo to najczęściej kwestia jednej linijki), act i assert to też po jednej linii.

Przy okazji, przez użycie klas zagnieżdżonych nie muszę w każdym teście powtarzać nazwy metody testowanej.

Przy czym ja rzadko mam potrzebę testowania jakichś zaawansowanych interakcji z bazą czy coś. U mnie serwis strzela do innych serwisów, wyciąga z nich dane, młóci wszystko w pamięci, zwraca wynik dalej.

1

Z jednej strony spoko, z drugiej strony trochę dziwne jest testowanie testów. Ale jak działa szybko i wymaga jednej linijki kodu, to można się w to bawić.
Jeżeli obawiasz się, że Twój mock zrobiony przy pomocy Mockito jest niepoprawny, to masz coś skopane niezależnie od Mockito.

Jaką masz pewność, że twoje mocki są poprawne, zwłaszcza po refaktorze czy głębszych zmianach w repo?

Jak piszesz mocki X to zgodnie z aktualnym kontraktem X. Tylko gdzie jest ten kontrakt? Gdyby był stabilny to można by go zawrzeć w dokumentacji (np w dokumentacji do metod), ale z jakiegoś powodu programiści wolą pisać tzw. "samodokumentujący" się kod, a nie dokumentację do niego. Szerzej problem stabilności kontraktu przy mockowaniu opisałem tutaj: Testy jednostkowe: strict vs loose mocks

0
Wibowit napisał(a):

Szerzej problem stabilności kontraktu przy mockowaniu opisałem tutaj: Testy jednostkowe: strict vs loose mocks

Magii tu nie odkrywasz, tylko to nie ma nic do Mockito. Jakbym się uparł, to mógłbym testować mocki robione przez when.thenReturn i też dostawałbym błędy przy zmianach kontraktu.

2

Magii tu nie odkrywasz, tylko to nie ma nic do Mockito.

Właśnie ma i to wprost jest z mockowaniem związane. Jeśli zmieni się kontrakt X (załóżmy, że samo zachowanie metod, a nie sygnatury):

  • a wszędzie mockujemy to posypią się tylko testy testujące realnego X wprost (bo akurat tam nie mockujemy), a testy używające zamockowanego X (używanego jako zależność dla klasy testowanej wprost) będą cały czas zielone, bo mają zahardkodowany stary kontrakt w expectach
  • a wszędzie używamy otestowanej testowej implementacji X (tak jak podałem powyżej, tymi samymi testami co realny X) to zmiany kontraktu spropagują się na wszystkie testy korzystające z X

Jakbym się uparł, to mógłbym testować mocki robione przez when.thenReturn i też dostawałbym błędy przy zmianach kontraktu.

Jakby to wyglądało w porównaniu do fake repo? Obstawiam, że pokracznie. To jest łatanie problemu z mockowaniem, a nie udowadnianie, że go nie ma.

0
Wibowit napisał(a):

Właśnie ma i to wprost jest z mockowaniem związane. Jeśli zmieni się kontrakt X (załóżmy, że samo zachowanie metod, a nie sygnatury):

  • a wszędzie mockujemy to posypią się tylko testy testujące realnego X wprost (bo akurat tam nie mockujemy), a testy używające zamockowanego X (używanego jako zależność dla klasy testowanej wprost) będą cały czas zielone, bo mają zahardkodowany stary kontrakt w expectach
  • a wszędzie używamy otestowanej testowej implementacji X (tak jak podałem powyżej, tymi samymi testami co realny X) to zmiany kontraktu spropagują się na wszystkie testy korzystające z X

Nie, nie ma. Jak w Twoim przypadku nie będziesz testował fakeRepo, to tak samo przy zmianie kontraktu wszystko się wysypie. To nie jest kwestia Mockito, tylko kwestia testowania mocka. "Mockowanie" a "Mockowanie przy użyciu Mockito" to dwie różne sprawy.

Jakby to wyglądało w porównaniu do fake repo? Obstawiam, że pokracznie. To jest łatanie problemu z mockowaniem, a nie udowadnianie, że go nie ma.

Zgadzam się, to pewnie byłoby pokraczne. Ale ponownie - jak chcę testować mocki, to mogę to zrobić z mockiem napisanym ręcznie i z mockiem od Mockito.

Tak naprawdę Ty sugerujesz koncepcyjnie to samo, co @Shalom. On nie chce mieć bezmyślnego mocka dla bazy danych, więc używa H2. Ty nie chcesz mieć bezmyślnego mocka dla repozytorium, więc klepiesz jakąś banalną implementację in memory. Te podejścia mają swoje plusy, ale dla mnie to się nie spina, bo:

  1. Testowanie z H2 nie jest testem integracyjnym. Ja chcę mieć infrastructure as a code, wrzucić kod na feature branch i niech agent zacznie stawiać wszystko w chmurkach i odpalać prawdziwe testy na prawdziwych komponentach. Może i H2 wystarczy ludziom do szczęścia, ja wolę odpalić wszystko z prawdziwym testem integracyjnym, a nie takim udawanym (szczególnie, że zwykłego SQL-a dawno nie używałem, raczej jadę na NoSQL, kolejkach, szynach itp).

  2. Skoro mam test integracyjny robiony automatycznie, to nie muszę się bać mockowania, bo prawdziwą bazę danych i tak przetestuję. Wobec tego spokojnie wrzucam stuby w testach jednostkowych, jest to szybkie, nie muszę bawić się w testcontainers, działa w pamięci, jest przenośne między systemami operacyjnymi, mogę łatwo debugować z IDE. Sporo zalet.

  3. Mocków raczej nie testuję. Owszem, zdarzało mi się pisać własne wydmuszki, ale i tak były one trywialnymi implementacjami SAM interface, przyjmowały coś przez konstruktor i potem zwracały z metody. Po czasie stwierdziłem, że takiego samego mocka dostanę robiąc mockito.whenReturn, a jak opakuję to w metodę pomocniczą (czy jakiś inny DSL), to jest to tak samo czytelne. Co nie znaczy, że kopię się z koniem i robię mockitowe mocki na setki linii albo z jakimiś czarami na argument captor czy coś. Jak robię większą żonglerkę, to klepię własną implementację interfejsu i tyle, ale dla banalnych rzeczy nie widzę powodu, dla którego miałbym się męczyć zamiast użyć Mockito. Nie wiem, jak to wygląda u Ciebie, być może podejmujecie inne decyzje na poziomie Interface Segregation Principle albo SRP, co potem wpływa na rozmiar mocków. A może po prostu piszecie aplikacje, które muszą robić duży ping-pong z zapisem i odczytem w repozytorium w jednym teście. U mnie to jest proste — wczytaj dane ze źródeł, przemiel, daj wołającemu — i przez to nie potrzebuję absolutnie żadnej logiki w mockach, toteż nie muszę ich testować.

0

Ludzie co piszą testy nie są pewni swojego kodu!

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