Testy i mockowanie

Odpowiedz Nowy wątek
2017-01-12 08:03
3

Ostatnio w dyskusji o Spring Beans wyszła ciekawa dyskusja (choć przez niektórych uznana za trolling) o testowaniu i mockowaniu. Jestem zwolennikiem mockowania, ale jako że zdanie @jarekr000000 jest odmienne, to chciałbym je lepiej poznać i podyskutować o tym.
Na wstępie zapraszam @jarekr000000 @somekind @scibi92 jako uczestników tamtej rozmowy, ale offc, post każdego jest mile widziany. W dyskusji prosiłbym o poruszenie jedynie tematów testowania i mockowania, ale nie konkretnych frameworków, w szczególności spring ;).

Załóżmy że stosujemy IoC bez żadnego frameworka DI i mamy do przetestowania klasę A.

class A{
    private final B b;
    private final C c;
    public A(B b, C c){/*przypisania*/}
    public X doSomething(Y y);
}

Klasa ta ma jedną metodę i w niej za pomocą 2 innych obiektów tworzony jest z parametru y wynik x. Ja w tym podejściu zamockowałbym B i C, tak żeby do testów trafiła tylko logika y. Robiąc ten test zakładam że:

  • JVM dla 2 + 2 będzie zawsze zwracała 4 i ogólnie działała tak jak to przewiduję.
  • Biblioteka testów jest napisana bezbłędnie
  • Biblioteka mocków jest napisana bezbłędnie

Dla mnie jest to idealny test jednostkowy, ponieważ zrobiłem minimum założeń bez których nie dałoby się napisać żadnych testów. Oczywiście te założenia mogą okazać się nieprawidłowe, ale wtedy problem jest globalny co do aplikacji i to tam trzeba go rozwiązać a nie w specyficznym teście.

Gdybym jednak przakazał tam prawdziwe instancje B i C to:

  • Nie byłby to test jednostkowy ale integracyjny. Uważam że test jednostkowy testuje możliwie najmniejszą jednostkę, a za integracyjny uważam taki który testuje zestawienie już 2 jednostek. Wiem że potocznie integracyjny to czasem od webserwisu do bazy danych albo jeszcze zewnętrznego systemu, ale gdy zestawiasz 2 klasy to już masz 2 odrębne moduły.
  • Test jest zależny tylko od testowanej klasy. Jeżeli B ma implementację B1 a zostanie ona zamieniona B2, który działa inaczej i w pewnym momencie zaczęłaby szwankować to mój test zacząłby się wywalać. Moim zdaniem jest nieprawidłowe działanie bo jeżeli nie działa X, to test X powinien się wywalić a nie test Y. Jeżeli B byłaby bardzo powszechną klasą i była zależnością 100 innych, to w przypadku jej awarii wywaliłoby się 101 testów zamiast jednego. Dla mnie jest to informacja że coś nie działa, a nie że klasa X nie działa.

Oczywiście można założyć że jest to dodatkowy test, ale moim zdaniem zamiast testować B w 100 klasach tak samo, lepiej napisać 10 dodatkowych testów do samego B żeby upewnić się że jest idealna skoro zależy od niej tak wiele.

link do tamtej rozmowy? - fasadin 2017-01-12 08:16
@krzysiek050: Opisałeś idealny test jednostkowy. A teraz poproszę o pytanie / problem do dyskusji. - vpiotr 2017-01-12 09:49

Pozostało 580 znaków

2017-01-12 08:22
Wielki Ogórek
0

Może Cię zainteresuje https://codurance.com/2015/05/12/does-tdd-lead-to-good-design/

Pozostało 580 znaków

2017-01-12 08:57
0

Pomijając ,że ta desuksja już była...

Załóżmy że stosujemy IoC bez żadnego frameworka DI i mamy do przetestowania klasę A.

Jeśli w jakimś momencie t - masz do przetestowania klasę A albo metodę M - to już coś popsułeś wcześniej. I to nie chodzi o TDD - tylko nonsens pojęcia testu metody albo klasy. Testuj funkcjonalność i będzie Ci łatwiej.


Bardzo lubie Singletony, dlatego robię po kilka instancji każdego.
edytowany 1x, ostatnio: jarekr000000, 2017-01-12 08:57

Pozostało 580 znaków

2017-01-12 09:02
1

Metoda odpowiada za jakąś funkcjonalność, więc testując ją, sprawdzam czy faktycznie robi to co zakładałem.

Nie koniecznie. - TomRiddle 2017-01-12 19:39

Pozostało 580 znaków

2017-01-12 09:06
1

Jak tak zaczniesz robić - to Ci problem mocków mocno zniknie. Przede wszystkim przestaniesz zakładać, że unit test - to test "jednej" klasy.


Bardzo lubie Singletony, dlatego robię po kilka instancji każdego.

Pozostało 580 znaków

2017-01-12 09:28
3

@jarekr000000 No jak ktoś zacznie robic testy integracyjne zamiast jednostkowych to faktycznie problem mocków zniknie, trudno się z tym nie zgodzić, niemniej sensu ani logiki w tym nie ma za grosz. Szczególnie kiedy "funkcjonalność" jest złożona i wymaga interakcji wielu obiektów a moze i różnych systemów. W ten sposób to można (i tak się robi) definiować testy akceptacyjne -> tzn mamy zestaw testów które dowodzą, że wymaganie funkcjonalne XYZ jest spełnione przez nasz system. I to jest faktycznie wystarczająco dobre, żeby stwierdzić czy system nam działa czy też nie. Ale nie sprawdza się w ogóle jeśli chcemy stwierdzić co konkretnie nie działa w systemie, kiedy jednak jakiś test akceptacyjny/integracyjny nie działa.
Oczywiście zaraz pojawią się głosy, że w takim razie testowany obiekt jest zbyt skomplikowany i należałoby podzielić to na jakieś fragmenty, tylko że znów testy tych fragmentów wcale nie dowodzą, że interakcja pomiędzy nimi jest poprawna, a to w tej chwili sprawdzamy tylko integracyjnie.
Wartość dodana z testu takiej klasy za pomocą mockowania zależności jest taka, że jak się cos wywala to wywala się dla bardzo małej jednostki -> jednej klasy czy jednej metody i od razu wiadomo co i gdzie się popsuło. Szczególnie że mocki dają dość potężne możliwości sprawdzenia warunków wykonania i zachowania. Jak się wywala test integracyjny to niekoniecznie jest to takie oczywiste co się stało. A już szczególnie kiedy wykonanie testu wymaga np. jakiejś interakcji zewnętrznej.


Na PW przyjmuje tylko (ciekawe!) zlecenia. Masz problem? Pisz na forum, nie do mnie.
edytowany 3x, ostatnio: Shalom, 2017-01-12 09:54
Pokaż pozostałe 2 komentarze
@jarekr000000: JEE 2 / Struts / XML FTW. - vpiotr 2017-01-12 10:31
@vpiotr: Z pozytywów to przynajmniej nie masz JSF :-) - jarekr000000 2017-01-12 10:35
@jarekr000000: wolisz Strutsy od JSF? Jakiś konkretny powód? - vpiotr 2017-01-12 11:04
@vpiotr: obydwa to katastrofy. Tylko Strutsy są prymitywnie proste i jest szansa, że w dłużej chodzącym projekcie ludzie jakoś ogarną idee i nie będą robić hacków na framework. Ludzi, którzy ogarniają JSF jest mało i prawie nikomu się nie chce czytać jak działa, a że framework nie jest trywialny to rodzą się koszmary. (Mam uraz po odkręcaniu projektu całego zrobionego na SessionScoped beanach, + wycieki pamięci, + nieśmiertelne próby łączenia JSF z portletami). - jarekr000000 2017-01-12 11:08
@jarekr000000: używanie SessionScoped to jak używanie globali. Dramat sam w sobie. - vpiotr 2017-01-12 11:25

Pozostało 580 znaków

2017-01-12 09:49
0

Jak mam komponent (unit) , który gdzieś potrzebuję - to przygotowuje dla niego testy funkcjonalne - klient nawet nie ma pojęcia, ze coś takiego jest - więc nie są to testy akceptacyjne.
Fakt - mniej więcej tak staram się je robić - minimum narzucania jak komponent ma działać - a tylko co ma zwracać w wyniku dla podanych inputów. Bo jak tak nie robisz... to poważnie utrudniasz sobie refaktoring (czyli nonsens).

To są po prostu testy jednostkowe. I kij mnie obchodzi z ilu potem klas zrobię ten komponent - może być i 10 (ale raczej z 1-5 -szczegół implementacyjny) - i nie mokuje sobie wszystkiego pod spodem - zwłaszcza jeśli tego nie widać na zewnątrz.
I jeszcze jedno - to jest zwykle bardzo mała jednostka....(w sensie linii kodu).

Ale rzucam Ci pewien hint : dużo masz metod void? Bo to też problem (poza samym Springiem, który Ci rozwala architekturę).

I po raz kolejny jak mam zewnętrzny system / IO to go zamockuję - po to mam mocki. Ale nie własne klasy i klasy kolegów z zespołu. Ale przykładowo bazy danych prywatnej dla systemu nie uznaję za zewnętrzny system (to szczegół implementacyjny, który warto pokryć testami).

Zresztą - ogólnie to rozumie twoje podejście. X lat temu tez byłem mockistą i używałem Springa. I dobrze mi to działało .. - do czasu refaktoringu.
Druga rzecz - 10 lat temu problemem był głównie brak testów - więc wolałem już uczyć zespół pisać trywialne testy na mockach, żeby coś chociaż było przetestowane.
Obecnie testy są standardem - natomiast pojawił się problem ich jakości. Testy, które testują tylko Mockito - to jeszcze pół roku temu był w zasadzie standard w jednym z moich projektów (takie cudo odziedziczyliśmy -przyznam, regularnie powód był do śmiechu). ( A widziałem podobnie testowane systemy kilka razy - jeden to nawet głównie moja wina - byłem mockistą :-) ).

Testy powinny jednak testować system -( jak najszerzej) + powinny ułatwiać refaktoring. To m.in. spowodowało, że stałem się przeciwnikiem mockowania jak popadnie.


Bardzo lubie Singletony, dlatego robię po kilka instancji każdego.
edytowany 6x, ostatnio: jarekr000000, 2017-01-12 10:02
Nawet mi nie mów o voidach. Poprawiam teraz taki kod po geniuszu, który zamiast ładnego obiektu stateless z metodami operującymi tylko na parametrach, zrobił singletona któremu najpierw ustawia pola a potem implementacja składa się z wywołania kupy voidowych metod (przecież wynik można pchać do pola klasy...) bez parametrów (no bo po co skoro wszystko w polach klasy...). A żeby było ciekawiej to jeszcze coś z tego dziedziczy. Dramat jakiś. - Shalom 2017-01-12 10:01
Ogólnie walka z voidem to teraz moje największe wyzwanie. Po walce z nullem, która była w sumie nudna. Tak naprawdę jeszcze często mi te voidy wychodzą (taki np. void sendEvent(e) ) - ale czuję, że da się to (i warto) ogarnąć. - jarekr000000 2017-01-12 10:11
Nie no czasem coś musi być void bo zwyczajnie nie ma sensu niczego zwrócić, szczególnie jeśli to jest tylko i wyłącznie jakiś side-effect. Nie dajmy się zwariować :P - Shalom 2017-01-12 10:16
@Shalom: no właśnie jakiś czas temu nabrałem wątpliwości i zacząłem nawet takie rzeczy przepisywać. To ciągle eksperyment i też czuję, że może być katastrofa (ale spoko: to nie w produkcyjnym kodzie robie). - jarekr000000 2017-01-12 10:22

Pozostało 580 znaków

2017-01-12 10:05
0

@vpiotr: Problem brzmi: Mockować czy nie i dlaczego.

@jarekr000000
Tylko jeżeli unit nie będzie zależał od niczego, to zwykle będzie bardzo wysoko i miał mnóstwo komponentów w środku. Możliwych ścieżek w takim wykonaniu może być setki, a przetestować trzeba każdą z nich. Testując małe komponenty jednostkowo i mockując zależności, masz zwykle kilka, lub nawet jedną ścieżkę.

Metod void unikam, bo nie da się ich przetestować. A springa w tej dyskusji nie ma :). IoT jest ręczne.

Pozostało 580 znaków

2017-01-12 10:27
0

mnóstwo komponentów w środku

Własnie dlatego uważam, taką oderwaną od kodu dyskujsę za besens. Na prostym, podanym przez autora przykładzie - nie widać żadnego sensu mockowania. Bo kod nie ma żadnego sensu.

Bo oczywiście, gdzieś czasem pojawia się granica gdzie warto Mocka wrzucić. Ale na pewno nie jest to na granicy klas.

Po drugie jeśli korzystacie nawet z dużego komponentu, który jest dobrze zdefiniowany i przetestowany - to po co zastępować go mockiem? Przecież to dużo niepotrzebnej pracy, skoro można użyć gotowego :-). Przecież jak będzie miał błąd - to najpierw wywalą się jego testy. Nie piszę, że tak zawsze trzeba robić - ale może warto czasem o tym pomyśleć?


Bardzo lubie Singletony, dlatego robię po kilka instancji każdego.
edytowany 1x, ostatnio: jarekr000000, 2017-01-12 10:28

Pozostało 580 znaków

2017-01-12 10:44
0

On jest jedynie zdefiniowany, ale niekoniecznie zaimplementowany lub tym bardziej przetestowany. Wiemy jedynie że ma jakąś funkcjonalność, ale sami jej teraz nie np. nie mamy, bo powstanie za 2 tygodnie. Będziesz czekał tak długi z otwartym tematem jednej funkcjonalności, skoro jej poprawność zależy od osoby która siedzi w budynku obok? A co jeżeli po tych dwóch tygodniach okaże się że zależność nie działa tak jak powinna, ale to Twój test się sypie? Oczywiście test jednostkowy.

Pozostało 580 znaków

2017-01-12 10:55
1
krzysiek050 napisał(a):

A co jeżeli po tych dwóch tygodniach okaże się że zależność nie działa tak jak powinna, ale to Twój test się sypie?

To by znaczyło, że po coś ten test się przydał :-) Doskonale !


Bardzo lubie Singletony, dlatego robię po kilka instancji każdego.

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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