pátek, prosince 07, 2007

Not so Test Driven...

Tuhle jsem tu psal o tom, jak mě BVer uvrtal do unit testování, a věcí okolo. Dokonce jsem na sobě zkoušel praktikovat i Test Driven Development, a nějak bych rád shrnul, co jsem zjistil.

Především chci říct, že když jsem báječný svět unit-testů objevil, říkal jsem si, jak jsem asi tak tisíc let za opicemi, a že tohle přeci musí používat každý člověk s trochou sebeúcty. Myšlenka testovat kód po malých částech ještě před tím, než zapomenete co má přesně dělat, nebo dokonce zjistit co a jak má dělat tím, že napíšete testy je tak skvělá a samozřejmá, že jsem byl zklamán, že mě nenapadla samotného.

Jaké bylo moje rozčarování, když jsem zjistil, že testování na úrovní nějakých malých komponent není vůbec samozřejmé, a že dokonce velká část lidí, kteří se živí programováním se k němu staví negativně, nebo dokonce odmítavě, a celkově v něm nevidí žádný užitek. Zkoušel jsem několika lidem několikrát opatrně vysvětlit, že čím dřív na chybu přijdou, tím dřív a rychleji ji odstraní, a i když unit testování není samospasitelné, že je to skvělá věc, a že by se jí měli věnovat.

Dokonce jsem i byl skoro úspěšný, když jsem jednoho (teď už bývalého kolegu) přesvědčil, aby si pár testů napsal. Čekal jsem, jak za mnou přiběhne s velkou krabicí belgických bonbónů, bude mi děkovně třást rukama a radostně poskakovat a povykovat, o kolik rychleji teď programuje a jak se kvalita jeho práce zvýšila a tak dále a tak podobně. Místo toho asi po třiceti minutách přišel s velmi správnou otázkou: "No a co mám jako dělat s tou databází?" I neváhal jsem, a začal ho zasvěcovat do tajů mock objektů, které okopírují rozhraní takové databáze, ale nebudou ve skutečnosti dělat nic jiného, než že na předem připravené otázky dají předem připravené odpovědi, načež zkontrolují, zda se jeho kód chová správně, a jeho kód zjistí jestli umí správně reagovat na divné stavy, do kterých se taková databáze může dostat. Čím déle jsem hovořil, tím víc zíval, až nakonec odešel, a o testování jsem od té doby spolu nemluvili.

Nerozuměl jsem tomu, protože napsat si mock-objekt na sériový port (ten z .NET) pro mě bylo opravdu zábavné, i když mi to, pravda, nějakou tu hodinku trvalo, a fakt, že jsem pak mohl pěkně ozkoušet co udělá celé komunikační rozhraní, které jsem tehdy psal, když místo očekávané zprávy přijde nějaká úplně jiná, mi pomohlo zkrátit ladění na pár desítek minut. Ještě méně jsem rozuměl odporu některých lidí, kteří se zabývají vývojem embedded aplikací, zvlášť když neměli k dispozici pořádný debugger.

Samozřejmě, že testování není zadarmo. Psát testy je někdy vážně nuda (třeba set/get metody s trochou logiky), někdy je to vyloženě masochismus (jednou jsem zkoušel, kolik úsilí by mě stálo dosáhnout 100% pokrytí kódu testy a vážně to nestálo za to, protože některé obzvláště vypečené chybové stavy je těžké vyvolat bez toho, abyste neomockovali půl operačního systému), nicméně celkově se rozumná míra testování vyplatí.

Projekt na kterém teď pracuji je poměrně agresivně naplánován, a popravdě, když jsem padesát minut psal mock na třídu zapisující do registrů, říkal jsem si, jestli to stojí za to. Nakonec se ukázalo že ano, protože jsem pak asi za deset minut odladil kód, který ten zapisovač používal, přičemž jsem odhalil asi pět velmi vypečených chyb, které by mě jinak stály spoustu ladění (určitě nejméně padesát minut, protože než bych na ně přišel, dávno bych zapomněl co jsem vlastně původně chtěl, aby kód dělal).

Rovněž jsem nabyl nezvratného přesvědčení, že i když TDD není pro mě (prostě nedokážu psát testy nasucho dopředu -- potřebuji alespoň nějaký kód, který pak třeba celý přepíšu, ale který mi dá vizuální představu jak bude celá věc fungovat a interagovat s okolím), jeho alespoň podvědomé používání vede k psaní lepších programů, které jsou lépe rozparcelovány, méně navzájem provázány, snáze udržovatelné, jednodušeji rozšiřitelné a tak dále. Myslím tím takové ty věci jako předávání objektů konstruktory, vyhazovaní výjimek, rozumné použití dědičnosti, využívání interfaců (interface nebo čistě abstraktní třída je grunt -- bez interfaců nikdy nemůžete vyměnit jeden objekt za druhý, resp. skutečný za mock) a podobné věci.

Chtěl jsem říct asi toto: jsem daleko od nejlepšího žáka Martina Fowlera, ale i když mám někdy pocit, že bych se měl na testování vykašlat, a raději smolit produkční kód, za který si zákazníci platí, vždycky se nakonec ukáže, že kdo testuje, ten vyhraje. A proto (spolu s klasikem) volám: Lidé, testujte!

4 komentáře:

BVer řekl(a)...

"..celkově se rozumná míra testování vyplatí."
To nejtezsi je vzdy prave najit spravnou miru.

S testy jsem ucinil tuto zkusenost: Cim "nize" v celkovem ramci je kod polozen, tim snaze se testuje a tim vice se vyplati psat zpusobem test-first. Stoupame-li vyse v urovnich kodu (smerem napr. k GUI a uzivatelum obecne), tim je tezsi nachazet pokazde spravne testovatelna rozhrani a pracnejsi vyrabet mocky.
Ale kde na vertikalni ose je onen bod, kdy ze prestavaji vyplacet unit testy a je treba je nahradit nejakym jinym prostredkem garance kvality? Presne tam, kde se potreby unit testu zacinaji "vnucovat" do produkcniho kodu a zacinaji prinaset nechteny overhead.

Obecne spatne se take pracuje v situacich, kdy je treba k testum mnoho dat nebo rozsahle konfigurace. Agilni metody veli: Najdes-li chybu, napis test, jenz ji nejjednodussim zpusobem reprodukuje a pak opravuj produkcni kod, dokud se nerozsviti zelene kontrolky. Nojo, jenze kdyz se chyba projevi az u 10MB datoveho souboru a jeho velikost neni mozne parametrizovat (tj. osidit)...?

A do tretice -- plati, ze ruzne jazyky maji ruznou podporu pro TDD.

"...o kolik rychleji teď programuje a jak se kvalita jeho práce zvýšila..."

S prvnim souhlasit nemuzu, s druhym ano. TDD produkuje kvalitnejsi kod, ale platime za to prave casem, v nemz piseme testy. Je to, jako kdyz piseme kod zaroven s bugfixy prvni verze -- stavime solidnejsi zaklady, ale vic betonu dele tvrdne ;-)

Obecne ale musim potvrdit, ze testovat se vyplaci. Kdo to nezkusil, at nekritizuje.

jIRI řekl(a)...

"Stoupame-li vyse v urovnich kodu (smerem napr. k GUI a uzivatelum obecne), tim je tezsi nachazet pokazde spravne testovatelna rozhrani a pracnejsi vyrabet mocky."

Ano, nalézt tu hranici, kdy se to ještě vyplatí je těžké. A také se to liší aplikace od aplikace. Některé se testují snáz, některé hůř. Obecně je velmi těžké mockovat libovolná externí zařízení se složitou interní logikou (a co teprv když používáš knihovny, neřku-li ovladače třetí strany), je těžké testovat GUI, když API/framework nad kterým je vyvíjíš není k takové věci uzpůsoben, opravdovou perlou je testovat cokoliv co používá více než jedno vlákno a tak dále a tak podobně.

Asi ke všem těmhle věcem existují jazyky/frameworky pro které existují hotová řešení, ale pokud si nemůžeš cílovou platformu vybrat (omezení daná třeba maximální velikostí výsledné aplikace atp.), pak je skoro nemožné stihnout vyvinout jak aplikaci, tak testovací rutiny v čase poskytnutém zadavatelem.

Nerad to přiznávám, ale je fakt, že jednoduchá GUI ve Win32 je asi lepší svěřit do péče testerovi, než se babrat s unit testy.

Nicméně mám pocit, že čím zkušenější ve vývoji software jsem, tím méně si dokáži představit, že bych vůbec netestoval.

Také jsem chtěl říct něco v tom smyslu, že každý máme své limity, a že čím je počet věcí, na které se můžeme "spolehnout" díky testům větší, tím více zbývá v hlavě místa na věci, které oportunisticky testovat nebudeme.

A ještě: mít tým 20 lidí, kteří se hrabou v jediné aplikaci (se všemi rozdíly ve schopnostech jednotlivých progišů) a netestovat je sebevražda.

"TDD produkuje kvalitnejsi kod, ale platime za to prave casem, v nemz piseme testy."

Moje teze je taková, že čas, který investuješ do testů ušetříš při ladění. Na jeden napsaný řádek kódu může připadnout až několik hodin ladění, a tohle testování umožňuje podchytit brzo, tedy dokud ještě víš co má ta která třída vlastně přesně dělat a proč.

Čili: jakékoliv testování je lepší než žádné, unit testy lze dodat i do aplikací, které zatím žádné neobsahují, to, že nějaký čas strávím při psaní testů se mně vrátí v okamžiku, kdy bych měl neotestovaný kód ladit.

jIRI řekl(a)...

Abych přihřál polívčičku mému oblíbenému jazyku: Sice jsem zatím neměl čas Specter (http://specter.sourceforge.net/) prostudovat důkladněji, ale vypádá to jako něco, co je k naší diskusi velmi relevantní (bohužel zatím nefunguje s Boo 0.8).

(A pro vás Ruby-sty je tu http://rspec.info/.)

BVer řekl(a)...

nám rubyistům mohu doporučit http://tinyurl.com/3xj6kd ,
což je přímo součást jazyka.

tím "piseme kod zaroven s bugfixy prvni verze" jsem myslel právě "čas, který investuješ do testů ušetříš při ladění", čili si rozumíme.

k tomu dodatečnému psaní testů tam, kde kód nevznikal stylem "failing test first": ano, jde to, ale vždy je tu pak riziko, že uděláme chybu v právě testu :)