poniedziałek, 3 czerwca 2013

YAGNI + Test First

czy warto pisać testy przed implementacją?

Testowanie samo w sobie przynosi korzyści, zazwyczaj w dłuższej (oby:) perspektywie czasu, ale prędzej czy później jesteśmy w stanie dostrzec ich wartość i nieraz oddychamy z ulgą, gdy widzimy, że ludzie pracujący nad kodem przed nami, poświęcili trochę czasu na napisanie solidnych zestawów. Lecz o zaletach posiadania testów już pisałem ostatnio (tutaj i ciąg dalszy :).

I o ile z tym, że posiadanie testów przynosi korzyści i jest to jedna z tych rzeczy, którą wielu programistów chce praktykować nie kłóci się nikt, o tyle nie wszyscy widzą jakikolwiek sens w pisaniu testów przed implementacją. Że niby sprawia, że kod jest lepszy, że jeszcze raz myślimy o problemie, że patrzymy na niego od innej strony, itp., itd.
Problem z tymi wszystkimi stwierdzeniami jest taki, że to są jedynie oklepane zwroty, którymi karmią nas bardziej doświadczeni, mądrzejsi znawcy tematu, którzy powołują się na własne doświadczenie i którym powinniśmy zaufać. I ja wcale nie twierdzę, że nie mają racji, czy też nie neguję ich kompetencji, ale człowiek (a programista też człowiek :) woli uczyć się na własnych błędach. Przynajmniej do momentu, gdy nie zobaczy konkretnego przykładu, który pokazuje mu, że jednak jest tak, jak podpowiada starszy kolega.

To może spróbuję rzucić jakimiś przykładami:)

drobne wyjaśnienie

Na początek chciałem zaznaczyć, że nie zamierzam rozwodzić się na temat techniki Test-Driven Development, ponieważ ona poza pisaniem testów przed implementacją zawiera kilka innych kroków. Ja natomiast chcę się skupić na idei Test First, która sprowadza się jedynie do tego wymienionego wyżej kroku (jeżeli jesteście zainteresowani różnicami pomiędzy TDD, a Test First to googlujcie :)

tak w życiu bywa...

O YAGNI i o tym, dlaczego reguła DRY jest istotna, pisałem niedawno (przeczytajcie zanim przejdziecie dalej, ponieważ będę bazował na przykładzie z tamtego wpisu) i mam nadzieję, że udało mi się przekonać przynajmniej niektórych z Was, że pisanie kodu na przyszłość, na podstawie przeczucia, kobiecej intuicji, czy też gwiazd jest jedynie stratą czasu i w większości przypadków (we wszystkich?) nie opłaca się.

Zdaję sobie również sprawę, że czasami po prostu kusi, żeby dodać tą linijkę, dwie, no, może krótką metodą, bo pasuje tu idealnie (niejednokrotnie sam popełniałem taki występek) i czasami nawet uda nam się to przepchać do kodu, który wyląduje na produkcji, bo cały zespół dojdzie do wniosku, że "skoro jest już napisane". Dlatego też dużo lepszym pomysłem jest zapobieganie takim wypadkom niż leczenie ich po fakcie, bo niestety leczenie często sprowadza się do machnięcia ręką (bo czas goni, bo trzeba by testy przepisywać, bo nadmiarowy kod nie wpływa na nic, nie szkodzi niczemu, itp.).

A jak zapobiegać?
Właśnie przez testowanie. Wyobraźcie sobie, że przed implementacją metody powstałyby testy. Na jakich byśmy się skupili? Sądzę, że powstałby tylko jeden, ten, który wynika bezpośrednio z wymagania, którym jest zastąpienie powtarzalnego ciągu wywołaniem metody.

Wyglądałby on zapewne tak:
/**
 * @test
 */
public function getName()
{
  $this->assertEquals('CONCAT(Customer.Name, " - ", IFNULL(Customer.Type, "UNSUPPORTED") as CustomerFullName', QueryHelper::getName());
}
Przychodzi Wam do głowy jeszcze jakiś test, który warto byłoby napisać? Mi nie. Metoda ma zawsze zwracać ten sam ciąg, więc to wystarcza w zupełności, testuje i pokrywa każdy możliwy przypadek.

Czy dopisywalibyście kolejne? Z przekazywaniem parametrów, których nie potrzebujecie? Z testowaniem tego, co zostanie zwrócone w zależności od przekazanego parametru? Jakaś ich walidacja? Wyrzucanie wyjątków? Śmiem wątpić :)

Dzięki takiemu podejściu wiemy dokładnie, co mamy napisać, a wszystko, co nas kusi, co chciałoby być napisane, nie powstanie, ponieważ jeden rzut okiem na testy wystarczy by odpowiedzieć nam na pytanie - Czy jest to coś, czego rzeczywiście potrzebujemy?