Blog
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!