Dominik Moštěk
9.12.2013

Proč psát unit test dříve než implementaci

tdd_flow
Fail, Pass, Refactor

Vždy, když mě napadlo napsat článek na toto téma během chvíle jsem si to rozmyslel. Reakce mého podvědomí na otázku v nadpisu byla totiž “vždyť to je jasné”, “to každý ví”, “o tom už bylo napsáno dost”. Ale nakonec tento článek vyšel, takže něco mě muselo přesvědčit. To něco bylo zjištění, že množství lidí v mém okolí tuto myšlenku nepodporuje, jiní jí věří, ale nepraktikují ji, a přesto, že toho bylo na téma testů napsáno už opravdu hodně, udělám to znovu.

Je to, co jsem naprogramoval, skutečně to, co jsem chtěl?

Představte si skokana do dálky, který se snažící kvalifikovat na závod. Pokud si nakreslí čáru k vytouženým sedmi metrům a skáče, má jasnou představu, čeho musí dosáhnout a má jednoduchou metodu, jak si ověřit úspěch. Stejné je to s testem. Ten nám říká, čeho chceme dosáhnout a taky nám oznámí, když toho dosáhneme. Pokud píšu test později, jak vím, že jsem skutečni splnil, co jsem chtěl, a že jsem místo toho jen neposunul kvalifikační čáru tam, kam jsem předtím skočil?

Testy vás vedou za ručičku

Test napsaný předem je jako maminka, která vás učí vařit. Říká vám co máte udělat dalšího. A když se vám to nepovede, řekne vám proč a nechá vás to opravit. Psaní kódu je pak velice jednoduché, intuitivní a zábavné. Jak navíc to, že kód funguje, testujete pokud nemáte test? Kompilace, deploy, start serveru, login, vyplnění vstupů, kontrola výsledků a pak znova a znova. Test spustím jednou klávesovou zkratkou a během pár sekund máme výsledek.

Zbytečný kód a code coverage

Na projektech se dost často (snad vždy) používají nástroje pro statickou analýzu kódu. Ty mimo jiné hlásí i pokrytí testy. Sledovat pokrytí s sebou nese jednu dobrou a jednu špatnou vlastnost.

Ta dobrá je ta, že pokud jsme napsali test předem (a pořádně), tak nám řekne, který kód je v implementaci zbytečný. A to jednoduše tak, že pokud je větev kódu nepokrytá testem, je téměr jisté, že tam nemá co dělat, protože pokud to po nás nevyžaduje test, tak k tomu není důvod. Jenže jak už to tak bývá, někteří toto číslo v anlýze pojmou jako mantru a začnou honit čísla, jen proto aby report dobře vypadal. A to je ta špatná vlastnost. Tedy stav, kdy se uspokojují nástroje a ne lidé (vzpomínáme na seo-servis.cz). Nepokrytá větev? Tak to musíme rychle napsat test, který ji pokryje. To že byl ten kód zbytečný, už nikdy nezjistíme.

Refaktoring

Máte funkční kód. Vše funguje jak má, ale ten kus kódu v cyklu by se vám hodil jinde a tady ta podmínka taky. Navíc hrozí že přibude ještě jeden stav, a tak to bude chtít nějaké změny. Je potřeba refaktorovat. Jak si můžeme být ale jistí, že jsme naší změnou nic nerozbili? Pokud ještě test nemáte vůbec, máte zaděláno na problém. Pokud ho máte, ale psaný až po implementaci, je dost možné, že jste ho napsali tak, aby pokryl implementaci a ne požadavky na ní. Implementace se změní, ale požadavky zůstavají. V jakém stavu potom bude test, těžko říct.

unit-tests

Test po je práce navíc, test před je zábava

Psát testy před implementací má kromě technických hledisek i psychologické. Pokud už mám hotový kód, který mi na webu vykreslil koláčový graf a vypsal kolik má Franta na účtu, tak je hotovo. No jo, jenže boss chce testy. Co se dá dělat, musíte se k tomu teda dokopat a dopsat je. To je hrozná otrava! Když si to “odbudete” na začátku, nebudete mít pocit, že děláte práci navíc, ale že děláte něco, co vám pomáhá.

Pokud nepíšeme testy předem, je možná dost zbytečné a drahé plýtvat časem programátora na psaní něčeho, co by mohl udělat nějaký program. Tento program by předloženým kusem kódu prohnal náhodné vstupy, zaznamenal by co se stalo a výsledky uložil. Takové řešení není nijak průlomové, takže už existuje a říká se mu Characterization testing (http://en.wikipedia.org/wiki/Characterization_test)

Test bych musel často měnit, protože zkouším různé implementace

Use the right tool for the job. TDD tu určitě není pro hrátky, kdy vlastně ani nevíme, co chceme, ale hrajeme si a doufáme, že na konci bude kloudný výsledek, který někde použijeme.

Pokud se testovaný subjekt opravdu mění tak často a zásadně, možná stojí za úvahu, zda nám tu nesmrdí špatné API, které vyplynulo dost možná právě z toho, že jsme ho navrhli až na základě implementace a ne jeho použití zvenčí.

Testy nejsou spása

Unit test je jen část skládačky. Sám o sobě je jen dobrým nástrojem, jak si ulehčit psaní kódu a refaktoring. Test vám taky neřekne, že že váš kód je bez chyb. Jen vám řekne, že kód který jste napsali, odpovídá zadání, které jste mu předložili ve formě testu. A když náš test za moc stojí, může to dopadnout třeba i takto (zdroj)

Závěrem

Každý chceme psát lepší kód, ale někdy je lepší nejdříve naostřit pilu, než začneme řezat.

Jiří Knesl nedávno napsal na twitter, že TDD je druhá nejvýznamější věc, která ho posunula jako programátora vpřed a já s ním naprosto souhlasím.

Kam dál

Ian Cooper: TDD, where did it all go wrong Making TDD Stick: Problems and Solutions for Adopters 3 Rules of TDD Knihy Test Driven Development: By Example – Kent Beck xUnit Test Patterns: Refactoring Test Code – Gerard Meszaros Test Driven: TDD and Acceptance TDD for Java Developers – Lasse Koskela Pokud ve vaší firmě ještě netestujete, zvažte školení testování od AspectWorks.

Vaše emailová adresa nebude zveřejněna

Komentáře

Děkujeme za váš komentář
Další
  • Jaromir Hamala

    Ahoj, s myslenkou clanku souhlasim, ale toto je tedy velmi odvazne tvrzeni: "Implementace se změní, ale požadavky zůstavají." Prave zmeny pozadavku v case je velmi vyznamny argument pro psani testu. Jak jinak poznat, ze zmena vlastnosti X nerozbila vlastnost Y?

  • Dominik Moštěk

    Souhlasím. Abych objasnil mé tvrzení. Mám požadavek, že metoda má vracet setříděnou kopii pole. Zjistím, že je sleep sort opravdu není to pravé ořechové a tak změním implementaci na bubble sort :) Požadavek (ověřovaný testem) zůstal, ale implementace se změnila. Pokud jsem psal test later, tak je dost možné že jsem testoval sleep sort a ne obecný sort podle mých pažadavků a proto se bez změny požadavků může začít test bezdůvodně červenat.