Jak to jest z tymi mockami?

1

Ostatnio rozpocząłem prace w zupełnie innej dziedzinę zarówno pod względem technologii jak i produktu. Piszę głównie w Go, trochę mikroserwisow, z którymi jeszcze nie miałem do czynienia. W testach masa rzeczy jest zamockowana. Z jednej strony wygląda to "ładnie", a drugiej okazuje się że są bugi których te testy najwidoczniej nie łapią.

Pracując w C++ trafiałem na takie projekty gdzie tych mockow praktycznie nie było, a jak już to stanowiły powiedzmy z 10% zawartości testów w bardzo specyficznych przypadkach.

Przechodząc do rzeczy. Kiedy warto stosować mocki? Znacie również jakieś przykłady kiedy te "atrapy" są nadużywane? Co wtedy zrobić jeśli chcę być dobrym samarytaninem i polepszyć kod w którym na codzień się obracam?

4

Kiedy warto stosować? Kiedy nie chcesz testować jakiejś części kodu (bo jest przetestowana osobno) lub na przykłąd łączyć do rzeczywistej bazy danych i robisz implementację która szybciej będzie udawać wynik tamtej części kodu.
Czy są nadużywane? Często i gęsto. Praktycznie we wszystkich projektach w jakich pracowałem były nadużywane - potem mamy testy wydmuszki które tworzą mocki i je wywołują nie testując praktycznie niczego poza frameworkiem do mockowania.
Powinno się używać jak najmniej, test powinien przejść przez jak najwięcej rzeczywistego kodu.
Co zrobić? Usuwać mocki, testować rzeczywisty kod. Sam będziesz wiedział kiedy trzeba użyć mocka.

6

Mocki można stosowac w przypadku zależności zewnętrznych IO kiedy nie robimy testów integracyjnych. Można od bidy stosowac nawet do wewnętrznych zależności, ale to w jakis skrajnych przypadkach.
Ale osobiście preferuje TestContainers i wtedy człowiek nie narzeka ;]

0

@obscurity: To czegoś tu nie rozumiem.
Przykładowo mamy A zależne od B (nie wnikając skąd ta zależność i czy faktycznie powinna istnieć). W takim razie robimy faktyczne testy B, a w testach A tworzymy sobie zmockowane B. Wydaje się proste jak budowa cepa. Skąd w takim razie bierze się ta patologia związana z mockami? Właśnie ze względu na jakieś niepoprawne zależności?

4
Seken napisał(a):

@obscurity: To czegoś tu nie rozumiem.
Przykładowo mamy A zależne od B (nie wnikając skąd ta zależność i czy faktycznie powinna istnieć). W takim razie robimy faktyczne testy B, a w testach A tworzymy sobie zmockowane B. Wydaje się proste jak budowa cepa.

A czy w tej historii B jest oddzielną jednostką, czy częścią jednostki A? Bo jeśli to drugie, to właśnie opisałeś nadużycie mocka.

Skąd w takim razie bierze się ta patologia związana z mockami? Właśnie ze względu na jakieś niepoprawne zależności?

Z dwóch powodów:

  1. Betonowanie kodu testującego z produkcyjnym. Potem nie można zrobić normalnej refaktoryzacji jednostki A (np. zamiast B użyć C oraz D), bo testy przestaną się kompilować. Musisz naprawić testy, zrobić mocki C i D zamiast B, a więc kończysz de facto z nowymi testami A oraz nową implementacją A. Mogłeś zrobić błędy w implementacji oraz odpowiadające błędy w testach, przez co kończysz z błędnym kodem oraz niewykrytymi błędami. Ergo poprzednie testy były bezużyteczne, bo nie dały Ci bezpieczeństwa refaktoryzacji.
  2. Stosując mocki zamiast faktycznych implementacji łatwo stworzyć testy, które tak naprawdę nie testują rzeczywistych sytuacji. Ponieważ mock robi to, co mu każemy, zamiast wywalić się, gdy np. klasa testowana przekaże mu bezsensowne dane, to efekt będzie taki, że mimo zielonych testów będziemy mieli błędny kod.
2

Generalnie, mocków używamy tam gdzie nie chcemy czegoś testować. Będą to przede wszystkim operacje na plikach, bazach danych, komunikacja sieciowa. Czasem również możemy chcieć zmockować inną część naszego kodu, żeby ograniczyć obszar testu. Dobrym pomysłem jest sprawdzanie test-coverage, czyli czy wszystkie ścieżki wykonywania są pokryte. Oprócz tego, wiadomo, testy integracyjne, itp. Poza tym, pisząc testy trzeba oczywiście pamiętać o warunkach brzegowych; w końcu test-coverage wykryje tylko czy przetestowane jest to co w kodzie, a nie domyśli się jakie przypadki nie są uwzględnione. :)

2

Mocki mogą znaczyć dwie rzeczy:

  • Dla niektórych to jest po prostu dostarczenie do klasy testowej implementacji (fake class, stub, anonimowa implementacja klasy) - ja takich używam często, zawsze kiedy potrzebuję przekazać swoją implementację w testach (nie zależnie od tego czy to coś pod spodem to jest coś z i/o, czasem, siecią). Po prostu, jeśli uważam że rozdzielenie dwóch klas jest wartościowe, to wsadzam swoje implementacje.
  • Dla innych to jest pisanie tworzenia dynamicznych mocków z użyciem jakiejś biblioteki, taki kod potem wygląda tak mock.getUsers().thenReturn(fakeUsers()). I takie tworzenie mocków z użyciem takiej biblioteki zaciemnia testy jak się tylko da, bo 80% kodu testów to jest rypanie się z mockami, i może 20% jest prawdziwego testowania. Nie mówiąc o innych problemach, jak to żę czasem powstają testy które testują tylko mocki, a nie prawdziwy kod.
0
Riddle napisał(a):

Mocki mogą znaczyć dwie rzeczy:

  • Dla niektórych to jest po prostu dostarczenie do klasy testowej implementacji (fake class, stub, anonimowa implementacja klasy) - ja takich używam często, zawsze kiedy potrzebuję przekazać swoją implementację w testach (nie zależnie od tego czy to coś pod spodem to jest coś z i/o, czasem, siecią). Po prostu, jeśli uważam że rozdzielenie dwóch klas jest wartościowe, to wsadzam swoje implementacje.

Klasy testowej (czyli tej w ktorej przebiegaja testy) czy testowanej? Jezeli mamy jakas metode bool foo() to po prostu tworzysz jej osobna implementacje na potrzeby testow, ktora zwraca na sztywno true/false? Dobrze rozumiem czy chodzi o cos innego?

1
Seken napisał(a):
Riddle napisał(a):

Mocki mogą znaczyć dwie rzeczy:

  • Dla niektórych to jest po prostu dostarczenie do klasy testowej implementacji (fake class, stub, anonimowa implementacja klasy) - ja takich używam często, zawsze kiedy potrzebuję przekazać swoją implementację w testach (nie zależnie od tego czy to coś pod spodem to jest coś z i/o, czasem, siecią). Po prostu, jeśli uważam że rozdzielenie dwóch klas jest wartościowe, to wsadzam swoje implementacje.

Klasy testowej (czyli tej w ktorej przebiegaja testy) czy testowanej? Jezeli mamy jakas metode bool foo() to po prostu tworzysz jej osobna implementacje na potrzeby testow, ktora zwraca na sztywno true/false? Dobrze rozumiem czy chodzi o cos innego?

Nie uważam żeby wkładanie mocków tam gdzie się da, było zasadne. Jeśli możesz napisać test który testuje cały moduł, i nie wymaga odizolowania go od niczego to fake'i i mocki nie mają sensu.

Ja dodaje fake'owe klasy tylko w tych miejscach, w których nie chcę uzależnić test suite'a od zależności. Wtedy tak, tworzę sobie powiedzmy new MemoryClient() i on jest inną implementacją Client, którą dostaje moja testowana klasa. Nie robię cyrków w stylu Client client = Mockito.mock(Client.class);.

8

Mocków nie należy stosować nigdy. W ostateczności prawie nigdy.
To prawie nigdy to, bo są oczywiste wyjątki:

  • testujemy odpalenie bomby atomwej,
  • testujemy coś z bazą danych oracle, a klauzula sumienia, nie pozwala na odpalenie tej kupy na własnym kompie (nawet w kontenerze) - wtedy mockujemy przez H2, (inne bazy podnosimy np. przez testcontainers)

I jeszcze jedno - nie polecam pisania testów jednostkowych.
Piszcie po prostu testy.

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