Ještě k testování

Know how

SEO, MVC, návrhové vzory, knihovny a AJAX už všichni umí, nebo jsou o tom alespoň přesvědčeni. O použitelnosti má ponětí stále víc vývojářů. Kdekdo se zaklíná „čistým kódem“… Jen jedna věc vzbuzuje zatím stále silný odpor – testování! Racionálně vzato to nedává smysl, takže příčina bude někde jinde…

S automatizovaným testováním kódu (a ve zbytku článku budu mít na mysli právě to) jsem se setkal v několika firmách. Nikde ho nedělali. Když jsem se ptal proč, dozvěděl jsem se vždy nějakou variaci na starý příběh o testování.

Starý příběh o testování

U nás ve firmě jsme vždycky psali kód tak, jak jsme uměli nejlíp. Postupně jsme se naučili pracovat s CVS a s knihovnami kódu a když byl čas, a že skoro nikdy nebyl, tak jsme zkoušeli i novinky. Mladý zapálený programátor nám jednou říkal, co se dozvěděl o Agile, jako že tam dělají ty scrumy a iterace a že programujou dva najednou, no to jsme se zasmáli, to jsou nesmysly, ale něco z toho jsme si vzali – zavedli jsme podle toho scrumu každodenní ranní porady.

No a tenhle vendelín jednou taky přišel s tím testováním. Já programuju patnáct let, takže nějaké zkušenosti mám. Od začátku mi bylo jasný, že to je spousta práce navíc, kterou nám nikdo nezaplatí. Kluci budou hodinu psát třídu a dvě hodiny test – jako k čemu to je? No, ale všichni to chválej, tak na tom asi něco bude, tak jsme to v létě, když bylo volnějc, zkusili. U jednoho takovýho projektu, co jsme dělali, jsme začali psát ke každý třídě testovací skripty.

Byl to šílenej vopich, kluci nadávali, že mají dvakrát tolik práce, že by za tu dobu byli už hotoví s celým projektem, a že je to jen zdržuje. Pár chyb to našlo, to sice jo, ale žádná sláva, na tu spoustu práce, co jsme s tím měli… Navíc to třeba vůbec nenašlo jasný chyby, co jsi v tom kódu viděl jen kdyžs ho přečetl! A nejhorší bylo, že u malých tříd to bylo OK, ale když jsme to dali dohromady a pustili proti databázi, tak se třeba ukázalo, že to vůbec nefunguje – a přitom ty unit testy byly všechny OK, OK, OK… Takovýhle testování je naprd. Navíc pak přišly nějaký změny a ty testy bysme museli stejně přepisovat, a to by se nám už vůbec nevyplatilo, udržovat dvojí kód, takže u nás jednoznačně #fail.

Vono teoreticky to zní hezky a pro takový ty malý třídy, kde se něco počítá, je to možná dobrý, ale v praxi to je k ničemu… Jediný testování, který má smysl, je to, že si každý zkusí, jestli to, co napsal, taky funguje. Takhle to dělají programátoři odjakživa a šlape to.

Inu, v praxi je k ničemu každý pracovní postup, který aplikujete mechanicky, bez pochopení jeho podstaty (taková kargokultická metodika). Vzít si z agile jen „ranní porady“ je nejjistější způsob, jak zjistit, že „to nefunguje“.

Ruku na srdce – kolikrát se vám stalo, že jste o něčem prohlásili, že to je „naprosto na houby“, až vám jednoho dne někdo ukázal, jak to používat, a vy jste museli uznat, že nástroj je výborný a „na houby“ bylo hlavně to, že jste s ním neuměli nebo nechápali, k čemu je? Mně mockrát.

V pozadí mnohých sporů a odmítání je leckdy nepochopení. Dovolte mi, abych byl tedy chvíli „advokátem pro testování“; mým cílem není přesvědčit vás v článku o tom, že byste měli testovat a že se vám to vyplatí, ale zkusit vyviklat některé protiargumenty, v jejichž základu je právě nepochopení. Čímž neříkám, že můj pohled na testování je jediný správný (to ale nedělají ani advokáti; místo toho to nazývají „právní názor“).

Zvolený obor

Testování je velmi široká oblast a mnoha lidem splývá, proto než se pustím do obhajoby, musím nejprve vymezit oblast, které se bude obhajoba týkat. Rád bych se věnoval jednotkovým (unit) testům. Jsou pravděpodobně nejznámější, nejčastěji vyjmenovávané, ale na druhou stranu hodně specifické.

Jednotkové testy jsou automatizované postupy pro otestování jednotky kódu (třída, knihovna, unit, skript, … – tedy něco, co lze samostatně testovat). Jejich cílem je strojově otestovat, zda daná jednotka dělá to, co dělat má, a zda nedělá něco, co dělat nemá. Je jasné, že automaticky můžeme otestovat pouze to, co se automaticky otestovat dá, resp. co lze automaticky otestovat snadno.

Automatizované testování nenahrazuje ruční; doplňuje ho.

U jednotek testujeme, zda:

  1. vrací správné hodnoty na správné vstupní údaje
  2. vrací správné hodnoty na mezní vstupní údaje
  3. legitimně zhavaruje tehdy, když zhavarovat má

Co to znamená? U jednoduchých funkcí zadáváme vstupní hodnoty a kontrolujeme výstupní, resp. chování funkce. U složitějších testujeme to, co testovat lze. Kupříkladu u třídy, která bude generovat CAPTCHA obrázek, nebudeme psát OCR, který bude vyhodnocovat, zda výsledek opravdu obsahuje požadované znaky, to je extremistický nesmysl. Otestujeme, zda při zadání dobrých vstupních údajů vygeneruje třída obrázek, jestli ten obrázek má patřičné rozměry a patřičný formát. To je snadné otestovat. To, jestli obrázek obsahuje opravdu daný text daným fontem, už nebudeme řešit unit testem; ověříme to metodou „kouknu a vidím“.

Ne každé testování je automatizované; ne každé automatizované testování je unit test.

Jednotkové testy by měly v ideálním případě otestovat každou metodu třídy, každou funkci v knihovně, každý řádek kódu, navíc takovým způsobem, který je nezávislý na zbytku systému či na vnitřních stavech. Každý „testovací případ“ by měl pracovat s čistou kopií jednotky.

Pokud jednotka používá nějaké komplexní funkce „zvenčí“, pak pro testování podstrčíme „mock object“, který se bude navenek tvářit tak, že opravdu funguje, ve skutečnosti ale jen vrátí testovací data. Řekněme, že budeme testovat HTML generátor, který generuje stránky ze záznamů v databázi. Namísto objektu, který přebírá data z databáze, podstrčíme „mock“ – jednoduchou třídu, která má stejné rozhraní, ale na getTextById() vrátí testovací „Lorem ipsum“. Jednotkové testy tak proběhnou nezávisle na okolí.

Unit testy nezjistí, jestli celý dům bude stát. Testujeme jednotlivé cihly, maltu, tvárnice, tedy základní stavební prvky, a ověřujeme, jestli fungují tak, jak od nich očekáváme.

Jednotkovými testy netestujeme, zda jednotka funguje spolu s ostatními; od toho jsou integrační testy. Netestujeme jimi ani to, jestli celá aplikace funguje. Očekávat, že jednotka pro generování HTML funguje, a tím pádem musí fungovat celý web, je bláhové. V dalším textu se nebudeme zabývat ani integračními testy, ani testováním aplikace, zůstaneme jen u automatizovaných jednotkových tes­tů.

ISO9001

K čemu nám tedy takové testování je? Nezjistíme tím, jestli to spolupracuje se zbytkem aplikace, nezjistíme, jestli aplikace funguje… Automatizované unit testy mají jinou hodnotu: jsou automatické (můžou tedy běžet bez zásahu člověka, např. na serveru jako hook u verzovacího nástroje), opakovatelné a jejich výsledky lze dobře zpracovat.

Trochu to připomíná známou (a mnohými proklínanou) normu ISO9001. Tato norma nezajišťuje, jak si mnozí lidé myslí, jakost výrobků. Tato norma je zaměřena na to, aby veškeré procesy byly jasně popsané, specifikované a opakovatelné. ISO9001 vám nezaručí, že při výrobě neuděláte chybu. Postup podle této normy pouze zaručí, že chybu uděláte vždy stejně (pokud je procesní), nebo že zjistíte, kde vzniká, protože jednotlivé kroky jsou přesně popsané. Ano, je to opruz, popisovat přesně všechny procesy, sepisovat lejstra o tom, co se dělá a jak se to přesně dělá. Ale když je někde chyba, můžete se postupů popsaných v lejstrech při hledání držet. Buď zjistíte, že někdo postup nedodržel, nebo že je v procesu chyba – a pak ji můžete opravit a popsat proces znovu.

S testováním je to podobné. Test není vaše ověření, že vše funguje; na to by byl leckdy opravdu drahý. Test je nástroj pro dlouhodobou udržitelnost kódu a pro rozumnou práci s ním. Dobře napsané testy dokáží odhalit problémy při zásahu do kódu. Většinu situací „tady přepíšu pár řádků, bude to ale fungovat stejně“, které vedou k prapodivným chybám, můžete s jednotkovými testy zachytit dřív, než si zavlečete do kódu skryté chyby.

Test je tak dobrý, jako jeho autor

Testování je jako španělská hospoda – najdete tam jen to, co si s sebou přinesete. Žádný test neobjeví v kódu nic, co autor nedokáže popsat. Myslet si, že unit test objeví chybu tam, kde nikoho nenapadlo, že by mohla být, je naivní.

Napsat dobrý test je trošku umění, především proto, že mnozí lidé postupují při ověřování chybně. Lidský mozek má tendenci hledat případy, které naši teorii potvrzují, namísto toho, aby hledal případy, které by jí vyvracely, kdyby fungovaly. Jinými slovy: musíme testovat nejen správnou funkčnost, ale i správnou nefunkčnost. 

Najít ale všelijaké kombinace, které by měly zhavarovat, vyžaduje opravdu zkušeného programátora s dobrou fantazií. Taky nikdo netvrdí, že napsat dobré testy je hračka!

Jednotkové testování není všespásné

Myslet si, že napíšu jednotkový test a knihovna bude automaticky dobrá a použitelná je bláhové. Myslet si, že jednotkový test zaručí kvalitní kód, je taky nesmysl. Přesto mnozí očekávají od jednotkových testů něco, co jim jednotkové testy nemohou nabídnout, a jsou pak rozčarováni z toho, že jejich očekávání nebylo naplněno. Často pak z neúspěchu viní testování jako takové.

Zopakujme si ještě jednou: Jednotkové testy slouží k automatizovanému, opakovatelnému a strojově zpracovatelnému testování izolovaných funkcí. Není to nástroj pro zajištění kvality nebo vhodnosti pro daný účel; nenahradí to dobrou analýzu ani dobrý návrh. Použijete je hlavně při dlouhodobé údržbě vlastního kódu. Naprosto neocenitelné jsou jejich služby ve chvíli, kdy napíšete „verzi 2“, která „by měla být kompatibilní s verzí 1“. Máte-li „verzi 1“ pokrytou dobrými testy, uvidíte na první pohled, jak to s tou kompatibilitou ve skutečnosti je.

Pokud píšete kód vždy důsledně jen na jedno použití, nasadíte ho do aplikace a pak už se k němu nikdy nevrátíte, tak pravděpodobně tuhle výhodu neoceníte. Po pravdě řečeno v takovém případě máte hlavně úplně jiný problém než to, že netestujete…

K čemu tedy?

Pokud se držíte metodiky TDD, tedy že nejprve píšete testy a až po nich kód, tak můžete brát psaní testů jako první použití vašeho nového kódu. Berte to jako příležitost zjistit, jak se s ním pracuje, a to ještě dřív, než ho opravdu napíšete. Uvidíte svůj kód očima jeho uživatele, což je zkušenost k nezaplacení. Třeba zjistíte, že budete muset něco v API změnit či upravit…

TDD bývá někdy některými hodnocena jako příliš ortodoxní. Asi není třeba být vždy a za všech okolností doslovný a pokud napíšete nejdřív jednoduchou knihovnu a až po ní testy, nebude to jistě žádné velké neštěstí. Jen pozor na to, že při obráceném postupu má člověk stále ještě v hlavě vlastní kód a mnohdy píše testy „na míru svému kódu a svým chybám“.

Kupříkladu píšeme funkci, u níž je parametr i, jehož hodnota smí být max. 10 (včetně). Při psaní se překoukneme a  do funkce napíšeme test, který vyhodí výjimku, když i<10. Pokud jsme nejprve napsali kód, tak máme mnohdy tendenci ověřovat, že pro i=9 projde a pro i=10 zhavaruje. Ve skutečnosti tedy testujeme to, že napsaný kód dělá to, co je v něm napsáno, nikoli že dělá to, co dělat má. Pokud začneme nejprve testem, pravděpodobně jej napíšeme správně.

Testy patří k bontonu!

Testy jsou v podobné roli jako dokumentace: programátoři mají odpor k vytváření, protože to je „neproduktivní práce“. Když člověk programuje, v hlavě mu letí myšlenky a na nějaké psaní dokumentace není čas… Maximálně tak nějaký ten komentář do kódu.

Propagátoři nových jazyků a čistého kódu hovoří o dokumentačních komentářích jako o samozřejmosti; měly by patřit do kódu stejně samozřejmě jako odsazování. Stejný pohled se začíná prosazovat i v oblasti testování. Pustit open source knihovnu do světa bez sady testů (a bez dokumentace) je v jistých kruzích už programátorské faux pas: k čemu mi je kód, který si můžu upravit, když nemůžu rychle zjistit, jestli mi úprava něco nerozbila?

Napsat dobrý test je nutnost, pokud chceme svým kódem přispět do většího projektu. I ve firmách, které nedělají open source, je často používáno automatické testování, ať už kvůli Continuous Integration, tak třeba i pro měření kvality práce programátorů – pokud někdo soustavně commituje změny, které neprojdou testem, lze to snadno dohledat a zjistit příčiny.

Testy, podobně jako dokumentace, nejsou v podstatě nikdy hotové a kompletní. To, že se v kódu objeví chyba, kterou test nezachytil, není důkaz toho, že jednotkové testování nemá smysl, ale toho, že byl test neúplný. Můžete se rozčílit na všechny propagátory testů a napsat jim to do diskusí, nebo můžete problém popsat testem; to druhé bývá rychlejší a smysluplnější. Stejně tak když vám kolega řekne, že mu vaše třída nefunguje za takových a takových podmínek: to je ideální příležitost ty podmínky nasimulovat v testu!

A nezapomeňte: dobrý test vám kryje záda, když jde do tuhého a hledá se viník!

Stejně ale…

Pro nás je to drahé a zdržuje to.

Zkusili jste si to, testy jste psali tak, jak se psát mají, všechno jste udělali správně, ale zdržovalo vás to. Knihovny totiž nikdy nepřepisujete a ty testy byste stejně spustili jen jednou. Pak asi ano, pokud jste si jisti, že jste všechno udělali správně, a přesto jste si spočítali, že se vám to nevyplatí, tak OK. 

Nám chyby v kódu nevadí.

Komu by vadily, že? Místo psaní testů vymyslíme, jak opravy kódu prodat zákazníkovi jako vícepráce, a vyděláme na tom!

Můj kód je vždy perfektní, protože jsem špičkový programátor.

Pardon, testem jsme vás nechtěli urazit. Víme, že jste špičkový stroj na kód, který není nikdy unavený, nikdy nedělá chyby, nikdy se nepřepíše, vždy je stoprocentně koncentrovaný – a že tomu věříte. Máte pro to ale i nějaký jiný důkaz než svoje tvrzení?

Všechny tyhlety takzvaný „metodiky“ jsou jen tlamocviky mladých frikulínů, které mají zakrýt, že vlastně vůbec neuměj‘ programovat…

Ale jistě… „Opravdový programátor“ napíše cyklus DO přes tisíc řádků, a nesplete se! Přidejme ještě „pravidla jsou pro slabochy“ a „čára není zeď“, ať to máme komplet. Ale upřímně – pokud si myslíte, že programování je umění, měli byste programy vystavovat na výstavách, a ne je cpát lidem do počítačů, aby s nima pracovali…

Shrnutí

  • Testy nejsou kouzlo; je to metoda. Když ji neumíte a děláte ji špatně, nebude vám fungovat, tak prosté to je.
  • Jednotkové testy testují to, co říkají: funkčnost jednotek kódu.
  • Automatizovaný test otestuje jen to, co do něj napíšete.
  • Jednotkový test nenahrazuje jiné metody testování; doplňuje je. Pokud chcete testovat, jak to bude fungovat dohromady, slouží k tomu integrační testy.
  • Jednotkové testy děláme proto, že jsou opakovatelné, automatizovatelné a jejich výstup lze strojově vyhodnotit.
  • Automatizovaný test nemá, na rozdíl od člověka, „své dny“ a vždy testuje vše tak jak má. Neznamená to ale, že můžou člověka plně nahradit – jen mu ulehčují mechanickou práci.
  • Testování není ladění.
  • Test je jen tak dobrý jako jeho autor; je-li autor lemrouch, je i test špatný.
  • Hodina vynaložená na psaní testu ušetří den hledání podivné chyby za půl roku. Pokud hodláte ještě za půl roku pracovat ve stejném oboru, zvažte tento aspekt.

Ke čtení naleznete mnoho různých materiálů, od teoretických po praktické ukázky. V češtině mě zaujala velmi podrobná Příručka o testování (pdf) od Anny Borovcové (blog).

Pokud vás problematika zajímá, využijte možnosti navštívit školení Jiřího Knesla na téma Testování v PHP (viz zkušenosti účastníků).

Martin Malý

Začal programovat v roce 1984 s programovatelnou kalkulačkou. Pokračoval k BASICu, assembleru Z80, Forthu, Pascalu, Céčku, dalším assemblerům, před časem v PHP a teď je rád, že neprogramuje…

Komentáře: 43

Přehled komentářů

danaketh Psaní testů
Martin Malý Re: Psaní testů
myshpa Re: Psaní testů
Martin Malý Re: Psaní testů
jáchym tip na testování v javascriptu
Martin Malý Re: tip na testování v javascriptu
Jáchym Re: tip na testování v javascriptu
Martin Malý Re: tip na testování v javascriptu
Aleš Roubíček Re: tip na testování v javascriptu
Mastodont Dotaz
Martin Malý Re: Dotaz
tdvorak Re: Dotaz
Michal Augustýn Re: Dotaz
jos Re: Dotaz
Martin Malý Re: Dotaz
Tomáš Herceg Unit testy
Martin Malý Re: Unit testy
Michal Augustýn Re: Unit testy
Martin Malý Re: Unit testy
vlk Re: Unit testy
Michal Augustýn Re: Unit testy
koubel TDD nedovolí prasit
tdvorak Re: Unit testy
Tomáš Herceg Re: Unit testy
drevolution Re: Unit testy
Ped Re: Unit testy
František Kučera Re: Unit testy
Michal Testy v PHP
rox Jeste se musime hodne ucit...
Opravdový odborník :-) Re: Ještě k testování
Martin Malý Re: Ještě k testování
Martin Malý Re: Ještě k testování
Opravdový odborník :-) Re: Ještě k testování
valnoha Re: Ještě k testování
Charvi Re: Ještě k testování
heptau Testy nad databazi
Michal Augustýn Re: Testy nad databazi
Aleš Roubíček Re: Testy nad databazi
František Kučera Re: Testy nad databazi
maio Re: Testy nad databazi
maio Test-driven development
kert Šedivé příspěvky
Kdyby Re: Šedivé příspěvky
Zdroj: http://www.zdrojak.cz/?p=3450