IT eSky.pl

RSS

Prawo, którego Twoje testy każą Ci przestrzegać

W TDD nie chodzi jedynie o poprawność kodu. Równie ważne, a nawet jeszcze ważniejsze, jest to, w jaki sposób TDD wymusza jego lepszy design. Pomiędzy kodem produkcyjnym i testami istnieje ścisłe sprzężenie zwrotne, specyficzny rodzaj symetrii: dobrze zaprojektowany kod ułatwia pisanie testów, w związku z tym testy jednostkowe popychają Cię w stronę lepszego designu.

Ta synergia jest szczególnie widoczna w przypadku Prawa Demeter.

Prawo Demeter można żartobliwie podsumować jako „Nie rozmawiaj z nieznajomymi”. Co to oznacza? To, że metoda M może wywoływać inne metody wyłącznie jeśli:

  • należą do tego samego obiektu co M lub do bezpośredniej zależności tego obiektu
  • należą do obiektu, który został utworzony wewnątrz M lub został przekazany do M jako parametr

Innymi słowy, nie powinniśmy „sięgać poprzez” nasze zależności i wysyłać komunikatów bezpośrednio do ich „wnętrzności”, co często podsumowuje się w uproszczeniu jako regułę „tylko jednej kropki”.

Jak to działa w praktyce?

Załóżmy, że chcemy przypomnieć klientowi o nadchodzącej rezerwacji hotelowej. Możemy sobie wyobrazić, że np. wyślemy mu maila z tematem zbudowanym przy pomocy następującej metody:

Na pierwszy rzut oka nie wygląda to źle. Kod wydaje się zwięzły i czysty. Bardzo łatwo może umknąć naszej uwadze, tym łatwiej, że jesteśmy przyzwyczajeni do łańcuchowania metod w jQuery i underscore/lodash.

Jednak powyższy kod łamie Prawo Demeter. Metoda nie ogranicza się do swojej bezpośredniej zależności (booking), ale sięga poprzez nią to obiektu hotel, a nawet jeszcze głębiej, poprzez hotel aż do obiektu location.

Taki kod jest bardzo kruchy. Zmiana w którymkolwiek z obiektów w łańcuchu booking->hotel->location go zepsuje.

Zobaczmy teraz co się stanie, gdy spróbujemy napisać test dla takiej metody.

Oczywiście chcemy by nasz test był właściwie wyizolowany, więc stub-ujemy wszystkie zależności. W wyniku otrzymujemy następujący test:

Ała! To już wcale nie jest takie zwięzłe. Setup stubów jest naprawdę skomplikowany i brzydki, i tym razem raczej nie da się tego nie dostrzec.

Zacznijmy jednak na próbę od przeciwnego kierunku. Jeżeli napisalibyśmy test najpierw, jak chcielibyśmy, żeby wyglądał?

Prawdopodobnie mniej więcej tak:

Teraz test jest elegancki. Pojedynczy stub, zamiast zagnieżdżonej hierarchii. Jak powinniśmy napisać metodę reminderMailSubject, żeby ten test zaczął przechodzić?

Też musimy się ograniczyć do pojedynczego poziomu zagłębienia:

No proszę! Co za niespodzianka! Przypadkowo spełniliśmy Prawo Demeter. Teraz nie tylko test jest łatwy do napisania, ale i oryginalna metoda jest ładniejsza i znacznie mniej krucha.

Ten przykład pokazuje dwie rzeczy:

Po pierwsze, demonstruje działanie w praktyce reguły „słuchaj swoich testów”. Naprawiając skomplikowany test zamiast się z nim borykać, przeważnie poprawiamy również design naszego kodu – co jest podwójnie korzystną sytuacją.

Po drugie, pokazuje jak ściśle Prawo Demeter jest powiązane z czystymi testami. Jeżeli borykamy się w teście z głęboko zagnieżdżonymi stubami lub mockami, wskazuje to jednoznacznie że przegapiliśmy naruszenie Prawa Demeter gdzieś w naszym kodzie. Musimy znaleźć i poprawić to miejsce, gdyż nie tylko ułatwi nam to życie podczas testowania, ale też uczyni nasz kod o wiele solidniejszym.


Jak to jest w Twoim przypadku? Przypominasz sobie sytuację, w której słuchając testów udało Ci się ulepszyć Twój kod? Podziel się swoim zdaniem w komentarzach poniżej!


Zobacz również