środa, 29 września 2010

Mockito i TDD - czy aby na pewno?

Witam!

Dwutygodniowe przerwy w pisaniu na blogu to jakaś klątwa Akademii :( Niemniej zważywszy na ilość pracy i obowiązków ostatnio cud, że choć tyle jestem w stanie zrobić. Temat dzisiejszego postu może trochę zmylić. Chociaż w pracy zapoznałem się z Mockito to rzecz raczej w tym, co umożliwia nam ten framework (pośrednio). Jakiś czas temu trafiłem na film ze spotkania Wrocławskiej Grupy Użytkowników Javy właśnie pt. "Mockito i TDD". O samym framework'u jest praktycznie niewiele powiedziane, pokazane nie jest praktycznie nic, ale prezentowane są bardzo mądre rady na temat pisania testów i samego podejścia do nich, toteż warto to obejrzeć:










O konstrukcji given-when-then dowiedziałem się ostatnio w trakcie pracy nad produktem z kolegą Damianem i muszę przyznać, że dzięki niej znacznie łatwiej pisze mi się testy! O wiele prościej wychwycić jest ten właściwy, pojedynczy element, który chcemy poddać testowaniu. W trakcie prezentacji wspominane są takie narzędzia testowe jak hamcrest, czy FEST-Assert. To pierwsze narzędzie znam równie krótko jak TestNG, ale w kilku przypadkach okazało się idealne, natomiast drugie bardzo mnie zaciekawiło i obiecałem sobie w ciągu najbliższych dni przyjrzeć się mu (może spisze swoje wrażenia w poście?).

A teraz aby nie przedłużać - życzę miłego oglądania :)
Pozdrawiam i do następnego razu!

9 komentarzy:

  1. Lukasz. To nie chlopaki z WJUGa prezentuja. Szczepan i Bartek sa z Krakowa i nie sa zwiazani z zadnym JUGiem.

    OdpowiedzUsuń
  2. @Grzegorz - ale prezentowali na sesji WJUG'a, więc tak ich nazwałem. Czy są z Krakowa, Wrocławia, Gdańska, czy Berlina - nieistotne tak długo, jak robią takie pouczające sesje :) Ale dziękuję za zwrócenie uwagi - zaraz poprawię.

    OdpowiedzUsuń
  3. Fajna prezentacja, ale ja w dalszym ciągu nie jestem przekonany do "mock'owania", jak można pisać testy na zasadzie czy dana metoda obiektu została wykonana 3 czy 5 razy i to w odpowiedniej kolejności...nieraz algorytmy się zmieniają i również interakcje między obiektami...liczy się wynik czyli czy metoda serwisowa spełniła swój kontrakt, a nie, że pod wpływem jej wykonania wywołało się 6 metod insert i 2 update

    OdpowiedzUsuń
  4. @Mirek:
    To dam Ci przykład z projektu, w którym teraz jestem:
    Projekt ma 3 warstwy: domena + dao, warstwa serwisowa, widok.
    Implementuję widok i nie ingeruję w warstwy niżej, jedyne co mnie w danym momencie obchodzi i co chcę sprawdzić to czy po kliknięciu w jakiś przycisk wywoła się odpowiednia logika.
    Przykładowo po kliknięciu "Zapisz" zapisuję nowo utworzonego użytkownika, więc robię tak:

    // mockuj serwis, którego nie testuje tutaj
    UserService service = mock(UserService.class)

    //wstrzyknij ten serwis do strony Wicketa, zeby strona z niego korzystala zamiast z prawdziwego
    inject(userPage, "userService", service)"

    wickettester.simulateSaveButtonClick();

    verify(service.saveUser(any(User.class), times(1));

    Piszę bez zaglądania w kod, więc małe błędy składniowe mogą być, ale idea jest taka: jeśli testujesz widok, to chcesz sprawdzić jego interakcję z częścią niżej, ta część niżej może nie być gotowa i masz tylko pusty interfejs, który wpinasz w widok. Piszesz test i co by się nie działo, widok masz gotowy i pewny, bo pokryty testem.
    A jeśli formularz będzie Ajaxowy i bardziej skomplikowany, bo wywołujący metodę na serwisie, gdy na jednym polu masz onChange i inne pole spełnia jakiś warunek, to łatwo coś popsuć przy zmianach, a taki test to pokryje i bez "martwienia się" warstwą serwisową funkcjonalność możesz uznać za zrobioną.

    OdpowiedzUsuń
  5. @Mirek - ponad to co napisał Tomek od siebie jeszcze dodam, że są sytuacje, kiedy bez mocków nie da się zwyczajnie przetestować. Wystarczy, że Twój komponent komunikuje się z inną aplikacją, która przykładowo w bloku statycznym próbuje komunikować się z innymi webserwisami i na tej podstawie kreować samego siebie. Gdyby nie PowerMock nie dałoby się takiego obiektu wykorzystać w testach.

    OdpowiedzUsuń
  6. @Tomasz Dziurko, @Łukasz "Smok" Rybka
    Zgadzam się sytuacje które podaliście sprawdzają się z mockami bardzo dobrze i zgadzam się, że w takich sytuacjach powinny być one stosowane.

    Tylko byłem w projekcie w którym ludzie testowali swoje serwisy na zasadzie, że mokowali wszystkie zalezności tego serwisu tzn. AccountService wykorzystuje innych 10 serwisów to mokujemy te 10 i sprawdzamy czy podczas wywołania metody na AccountService wykonały się odpowiednie wywołania na tych 10 serwisach i to w odpowiedniej kolejności. Kod takiego tworu jest strasznie brzydki i sam pomysł testowania w taki sposób mi się nie podoba.
    Zastanawiam się jak postępujecie w takich sytuacjach

    OdpowiedzUsuń
  7. @Mirek
    Hmm... trudno mi coś doradzić co na pewno będzie miało rację bytu, bo pewnie są sytuacje, że takie testy są potrzebne, ale...
    moim zdaniem, jeśli w serwisie mamy w kolejnych liniach kolejne wywołanie i coś w tych klimatach testujemy to trochę kiepskie podejście, bo testuje się rzecz oczywistą i idąc tym tropem należałoby testować gettery i settery :)
    Ja przy takim skomplikowanym teście mockowałbym tylko to coś na końcu i sprawdzał, czy po wykoaniu testu końcowy stan finalny tego bytu jest zgodny z oczekiwaniami, czyli że metody i serwisy, które miały coś na nim zrobić to zrobiły.
    Oczywiście to taka porada na sucho i może okazać się zupełnie oderwana od konkretnej sytuacji u Ciebie :)

    OdpowiedzUsuń
  8. @Tomasz Dziurko
    Dzieki za rade właśnie takie coś wykonuje, ale na zasadzie, że uruchamiam test który wywołuje pewną metodę serwisową i na końcu sprawdzam czy stan bazy danych jest prawidłowy tzn. taki jak oczekiwałem...tylko tutaj pojawiają się problemy bo testy takie wymagają już kontenera EJB bazy danych itp....choć z drugiej strony testy takie najwierniej symulują środowisko produkcyjne

    OdpowiedzUsuń
  9. @Mirek
    Jak coś ma zależność od 10 innych serwisów, to najprawdopodbniej znaczy że to coś robi za dużo i wymaga refaktora. A wtedy i testy stają się bardziej przejrzyste.

    OdpowiedzUsuń