Blog

14 listopada 2014 Wojciech Zawistowski

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

Tagi:

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:

reminderMailSubject: function(booking) {
var city = booking.getHotel().getLocation().getCityName();
return 'See you in ' + city + ' tomorrow!';
}

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:

describe('reminder mail subject', function() {
it('welcomes a guest to the city of the hotel', function() {
var booking = {
getHotel: function() { return {
getLocation: function() { return {
getCityName: function() { return 'SomeCity'; }
}; }
}; }
};

expect(reminderMailSubject(booking)).to.contain('SomeCity');
});
});

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:

describe('reminder mail subject', function() {
it('welcomes a guest to the city of the hotel', function() {
var booking = { getDestinationCityName: function() { return 'SomeCity'; } };

expect(reminderMailSubject(booking)).to.contain('SomeCity');
});
}

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:

reminderMailSubject: function(booking) {
var city = booking.getDestinationCityName();
return 'See you in ' + city + ' tomorrow!';
}

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 na blogu

09.09.2022
Marcin Jahn
It’s Not Just HTTP It’s Not Just HTTP

In today’s world of cloud-based solutions, distributed systems, and microservices-based architectures, network communication is a...

23.08.2022
Adam Mrowiec
Konferencja IPC 2022 Berlin Konferencja IPC 2022 Berlin

Pandemia wreszcie się kończy, dlatego w tym roku postanowiliśmy wrócić do naszych wyjazdów na konferencje....