Testy - obywatele pierwszej kategorii

Mam wrażenie, że to może być jakieś fatum, że trafiam na blogi pisane przez ludzi, którzy poddali się negatywnemu działaniu nieszczęsnych dyscyplin, które z kolei doprowadziły ich do całkowitego zaprzestania pisania testów jednostkowych.
Ten blog jest przykładem jednego z takich.


Poniższy tekst jest luźnym tłumaczeniem wpisu z bloga Roberta Cecila Wujka Boba Martina stąd:
Proszę o komentarze, gdyby ta luźność była zbyt daleko posunięta.


Autor opowiada o tym, jak jego testy są wrażliwe, ponieważ mockuje wszystkie współpracujące odwołania zewnętrzne. (ech). Każdorazowo, gdy odwołania zewnętrzne się zmienią trzeba zmieniać mocki. I to oczywiście czyni testy jednostkowe wrażliwymi.

Autor opowiada nam, jak porzucił testowanie jednostkowe i zaczął robić coś, co nazywa “Testowaniem Systemowym”. W jego słowniku, “Testy Systemowe” były po prostu testami, które były bardziej end-to-end niż “testy jednostkowe”.

A więc najpierw trochę definicji. Wybacz mi moją nieposkromioną pychę, ale jest teraz tyle różnych definicji “testów jednostkowych”, “testów systemowych” czy “testów akceptacyjnych”, że ktoś powinien podać jedną autorytarną definicję. Nie wiem, czy te definicje się przyjmą; ale mam szczerą nadzieję, że jakimś definicjom uda się to przyszłości.

  • Test Jednostkowy: Test napisany przez programistę, aby upewnić się, że kod produkcyjny robi to, co powinien. (Na ten moment pomińmy fakt, że testy jednostkowe także ulepszają projekt aplikacji, itd.)
  • Test Akceptacyjny: Test napisany przez “biznes”, aby upewnić się, że produkcyjny kod robi to, co powinien. Autorami tych testów są ludzie od strony biznesowej, albo ludzie od strony technicznej, którzy reprezentują biznes, np. Analitycy Biznesowi i Testerzy.
  • Test Integracji: Test napisany przez architektów i/lub liderów technologicznych, aby upewnić się, że podzespół składników systemu działa prawidłowo. To są testy kanalizacyjne. Nie są zestawem testów reguł biznesowych. Ich celem jest potwierdzić, że podzespół jest zintegrowany i skonfigurowany właściwie.
  • Test Systemowy: Test integracyjny napisany , aby upewnić się, że bebechy całego zintegrowanego systemu działają tak jak wcześniej zaplanowano. Są to testy kanalizacyjne. Nie są zestawem testów reguł biznesowych. Ich celem jest potwierdzić, że system jest zintegrowany i skonfigurowany właściwie.
  • Mikro-test: Termin ukuty przez Mike’a Hill’a (@GeePawHill). Test Jednostkowy napisany w bardzo małym kontekście. Celem tego testu jest sprawdzenie jednej funkcji albo małej grupy funkcji.
  • Test Funkcjonalny: Test Jednostkowy napisany dla szerszego kontekstu z odpowiednim zestawem mocków dla powolnych komponentów.

Biorąc pod uwagę te definicje autor wcześniej wspomnianego bloga zrezygnował z pisania źle napisanych mikro testów, na korzyść źle napisanych testów funkcjonalnych. Dlaczego źle napisanych? Ponieważ w obu przypadkach autor opisuje te testy jako połączone do rzeczy, do których nie powinny być połączone.
W przypadku mikro-testów używał za dużo mocków i mocno powiązał swoje testy z kodem produkcyjnym. To oczywiście powoduje, że testy są kruche.

W przypadku testów funkcjonalnych, autor opisuje je jako całą drogę od Interfejsu Użytkownika do Bazy Danych i odnosi się do faktu, że były powolne. Cieszy się kiedy jego testy czasami działają tylko 15 minut. 15 minut to o wiele za dużo, jak na szybki feedback, który testy jednostkowe powinny nam dostarczać. Praktycy TDD nie mają w zwyczaju czekać na system ciągłej integracji, tylko po to, żeby stwierdzić, że testy przechodzą.
Doświadczeni praktycy TDD rozumieją, że ani mikro-testy, ani testy funkcjonalne (ani nawet testy akceptacyjne) nie powinny być mocno połączone z implementacją systemu. Powinny (tak jak autor bloga zachęca nas) być rozważone, jako część systemu i “... traktowane jako obywatele pierwszej kategorii: [One] powinny być traktowane w taki sam sposób, jak kod produkcyjny”.

To, czego wydaje się, autor bloga nie dostrzega, to to, że obywatele pierwszej kategorii systemu nie powinni być ściśle połączeni. Ktoś, kto traktuje swoje testy jako obywateli pierwszej kategorii podejmie wiele starań, aby zapewnić, że te testy nie są mocno powiązane z kodem produkcyjnym.

Rozluźnianie powiązań mikro-testów i testów funkcjonalnych z kodem produkcyjnym nie jest szczególnie trudne. Rzeczywiście wymaga trochę zdolności w projektowaniu oprogramowania i trochę wiedzy na temat technik rozłączania. Zasadniczo spora dawka Projektowania Zorientowanego Obiektowo i odwrócenia zależności, razem z rozsądnym użyciem Wzorców Projektowych (jak Fasada czy Strategia) wystarczą, aby rozluźnić nawet najbardziej szkodliwy zestaw testów.

Niestety, zbyt dużej liczbie programistów wydaje się, że zasady dla testów jednostkowych są inne - że testy mogą być “śmieciowym kodem” napisanym przy użyciu dowolnego sposobu, wybranego ad hoc, jaki tylko ci programiści uznają za stosowny. Także zbyt wielka liczba programistów czyta książki na temat mockowania i kupili te książki w przekonaniu, że narzędzia do mockowania są nieodłączną i potrzebną częścią testowania jednostkowego. Nic nie może być dalej od prawdy.

Ja, dla przykładu, rzadko używam narzędzia do mockowania. Kiedy potrzebuję mocka (albo, raczej Dublera Testowego) piszę go sam. Nie jest trudno pisać takie dublery testowe. Moje IDE bardzo mi w tym pomaga. Co więcej, pisanie Dublera Testowego zachęca mnie do pisania testów jednostkowych bez użycia Dublerów Testowych, no chyba że to jest bardzo potrzebne. Zamiast używać Dublerów Testowych, odchodzę na chwilę od pisania mikro-testów i piszę testy, które są ciut bliższe testom funkcjonalnym. To także pomaga mi rozdzielić testy od wnętrzności kodu produkcyjnego.

Podsumowując, powodem tego, że ludzie poddali się z pisaniem testów jednostkowych jest zwykle to, że nie podążali za powyższą radą autora. Nie traktowali testów jako obywateli pierwszej kategorii. Nie traktowali testów w taki sposób, aby były one nieodłączną częścią systemu. Nie utrzymywali standardów dla tych testów jakich utrzymywali dla innych części systemu. Zamiast tego pozwolili testom się psuć, stać się mocno powiązanymi z kodem, stać się niezmienialnymi, kruchymi i powolnymi. I wtedy, w wielkiej frustracji, porzucili testy.

Morał: Zachowaj swoje testy czyste. Traktuj je jako obywateli pierwszej kategorii w Twoim systemie.