O automatických testoch som na svojom blogu popísal už veľa. Aj o tom, že existuje niekoľko ich kategórií. Čo som zatiaľ nespomenul, je známa pyramída automatických testov. Tá hovorí, koľko z každej kategórie testov by ste mali v projekte mať, aj kde začať (pyramídy sa zo zásady budujú odspodu smerom hore). Boli časy, keď som na túto pyramídu pozeral ako na kus teórie. Ale po skúsenostiach z niekoľkých projektov ju začínam brať veľmi seriózne. Mal som totiž možnosť zažiť projekty, kde sa rozhodli si zjednodušiť život. A preskočili prvú vrstvu – jednotkové testy.
Najprv by bolo asi dobré povedať, čo vlastne tú pyramídu tvorí. Úplne dole sú jednotkové testy – unit testy. Tie sú písané často v rovnakom programovacom jazyku ako samotný kód. Hlavná myšlienka jednotkových testov je, že jeden test by mal testovať práve jednu metódu v jednej triede. Akýkoľvek iný kód, ktorý by sa mohol spustiť, by mal byť izolovaný pomocou tzv. mockovania.
Nad jednotkovými testami sú testy služieb (hovorí sa im tiež API testy alebo integračné testy). Ich úlohou je testovať celú sústavu objektov a hlavne to, či tieto objekty správne spolupracujú (to je dôležitý bod na zapamätanie – o chvíľu sa dostaneme k tomu, prečo). Sú to testy, ktoré sa často spúšťajú voči API kódu alebo voči vonkajšiemu rozhraniu (REST, SOAP …) servera.
Špičku pyramídy tvoria UI testy. Tie testujú komplet aplikáciu vrátane používateľského rozhrania. Často sa tu pracuje s nástrojmi, v ktorých je možné testy nahrať a potom ho znova prehrať pričom program nie len kliká (píše) do testovaného programu ale aj overuje stav obrazovky.
Fajn, tak v čom je problém? Problém je v tom, že automatické testovanie je súčasťou budovania kvality v aplikácii. A práce na kvalite sa odrežú z projektu ako prvé, keď treba šetriť. V takom prípade to ale môže vyzerať, že existuje riešenie. Veď namiesto postupného písania jednotkových testov pre XYZ tried sa napíše niekoľko integračných testov – jednotkové testy sa preskočia. Integračné testy totiž spúšťajú kód niekoľkých tried naraz a tak ho de-facto testujú. Na čo sa potom babrať s podrobným testovaním? Videl som niekoľko projektov, kde takýto prístup utopil continuous integration server v červenom mori failed buildov.
Na vysvetlenie, prečo je to zle, sa pokúsim použiť metaforu. Predstavte si, že vašou úlohou je zo súčiastok zostaviť auto a potom ho otestovať (teda či naštartujete a auto sa pohne). A predstavte si, že by ste to robili tak, že by ste to auto zložili z jednotkových súčiastok bez toho, aby ste sa na nich poriadne pozreli a potom by ste ho skúsili naštartovať. Proste by ste naslepo brali súčiastku za súčiastkou a auto skladali.
Pri prvom pokuse by auto nenaskočilo, lebo v motore je niečo zle. Tak by ste ho postupne rozoberali, až kým by ste nenašli chybnú súčiastku, opravili ju a potom ho znova zložili a skúsili naštartovať (opäť bez kontroly ostatných súčiastok). Ak by ste pri montáži každú súčiastku kontrolovali, tak by ste tú chybnú našli hneď a súčiastku opravili. Keďže ale testujete všetky súčiastky štartom motora, tak moment kedy sa dozvedáte, že niečo (v prvom momente ani neviete čo) je zle je vtedy, keď už je auto zložené.
Vymeníte teda súčiastku, opäť auto zložíte a skúšate štartovať. Opäť nejde. Asi tam niekde bude ešte nejaká chyba. Keby ste len vedeli, kde presne. Totiž jediná informácia čo máte je, že motor neštartuje. A tak vám neostáva nič iné, len motor rozobrať znova a hľadať to, čo je zlé. Ručím vám za to, že v tomto momente vám motivácia niečo ďalej automaticky testovať začne rapídne klesať. Vitajte v svete jednotkové-testy-nemáme-testujeme-rovno-API projektov.
Ono to na prvý pohľad vyzerá strašne super. Jedným testom mám toho strašne veľa otestované. Problém je v tom, že integračné testy sú len doplnkom jednotkových testov, nie ich náhradou. Účelom integračných testov totiž nie je testovať to, či trieda funguje správne, ale to, či triedy medzi sebou správne spolupracujú. Ale to má zmysel testovať len vtedy, ak viete, že jeho jednotlivé časti dobre fungujú.
Aj napriek tomu by sa mohlo stále zdať, že integračné testy vedia z časti nahradiť jednotkové. Veď v konečnom dôsledku spúšťajú kód a overujú nejaký výsledok. Problém je, že testovanie nejakej väčšej časti systému nie je jednoduchá činnosť a cenou za to je, že tieto testy majú svoje nevýhody:
1. nastavovanie prostredia pred spustením – často sú to testy, ktoré v konečnom dôsledku pracujú s databázou, takže im treba pripraviť databázu so správnymi údajmi. Už toto samo o sebe je celkom výzva pre automatizáciu. Okrem toho to môžu byť rôzne konfiguračné súbory alebo multimediálne údaje niekde na disku. Nejaké nastavenia vyžadujú aj jednotkové testy, ale tie nastavenia sú pre jeden test omnoho jednoduchšie a väčšinou in-memory, kde sa dajú jednoducho vyrobiť aj upratať.
2. nehovoria v čom presne je problém – pri chybách tieto testy často skončia s niečim takým ako „služba nevrátila žiadnu objednávku aj keď mala vrátiť 4 záznamy“. Alebo „zápis údajov používateľa zlyhal“. Vám neostáva nič iné len pozrieť log alebo skúsiť test spustiť v lokálnom vývojovom prostredí. Niekedy to môže byť väčšia množina tried, ktoré musíte skontrolovať kým nájdete tú, ktorá má problém. Naproti tomu vám jednotkový test (ak ich píšete tak, ako sa má) povie „metóda X v triede Y vrátila zlý výsledok“. A vám ostáva max 100 riadkov kódu (dúfam, že väčšie metódy nemáte), ktorý treba prezrieť.
3. trvajú dlho – častokrát to býva spôsobené tým, že je potrebné pred každým testom nastaviť prostredie (pripraviť údaje) a potom po sebe zase upratať. A to často vyžaduje zápisy na disk, čo je pomalá činnosť. Alebo sa môžete vydať inou cestou a urobiť jednu databázu nad ktorou pustíte postupne všetky testy. Vtedy ale výsledok predchádzajúceho testu ovplyvňuje tie nasledujúce. Je to ako keď sa pokúsite hasiť oheň olejom. Naproti tomu jednotkové testy bežia v pamäti a za niekoľko sekúnd ich viete spustiť aj niekoľko stoviek. Tu je dôležité povedať o železnom pravidle, že pri continuous integration by server mal dať odpoveď o výsledku integrovania posledných zmien do 10 minút. Pri integračných testoch môže byť toto dosť ťažké dosiahnuť.
4. nestrážia štruktúru kódu – integračné testy testujú rozhranie. Je im jedno aké špagety vo vnútri máte, ak rozhranie vráti výsledok. Jednotkové sú opačný prípad – nútia vás vytvárať malé jednoúčelové metódy, používať medzi objektami slabšie väzby atď. Je veľmi ťažké mať aplikáciu pokrytú jednotkovými testami a pri tom mať nízku kvalitu kódu (aj keď – ruku na srdce – videl som už aj niečo také)
5. upravovanie pri zmene kódu je komplikovanejšie – ak zmením triedu A a ona má jednotkový test (triedu Atest), tak viem presne, ktorý jednotkový test mám upraviť. Ale ako zistím, ktoré integračné testy v konečnom dôsledku spúšťajú triedu A a ktoré z nich mám upraviť? Veľa šťastia pri hľadaní.
Napriek tomu všetkému sú integračné testy povinná súčasť automaticky testovaného projektu. Dokážu totiž perfektne plniť svoj účel – testovať, či triedy správne fungujú ako systém. Ale v čase, keď už bežia, by kód mal mať za sebou jednotkové testy a teda viete, že z veľkej pravdepodobnosti každá trieda robí to, čo vývojár zamýšľal. Dôležité je postupovať po pyramíde testovania odspodu nahor.
Ako som spomínal, videl som niekoľko projektov, ktoré preskočili jednotkové testy („ … je s tým veľa práce. Cez jMeter to otestujeme rýchlejšie všetko naraz …“) a veselo začali zavádzať integračné testy. Výsledok bol, že vznikla sada testov, ktorá trvala veľmi dlho a často padala (stále dookola padá ten istý test, pričom ale príčina je iná). Zostavovací server je často červený (v dôsledku čoho ľudia strácajú citlivosť na červenú farbu na jeho dashboarde) a upravovať tie testy dostávajú vývojári za trest.
A najhorším dôsledkom toho všetkého je, že ak niekto zažil len takéto projekty, tak má jednoducho pocit, že automatické testy sú nezmysel (a ja sa mu nedivím). A preto rada na záver: ak nie je čas písať jednotkové testy, tak ich nepreskakujte. Ak nemôžete mať jednotkové, tak potom radšej žiadne. Integračné testy bez jednotkových sú ako dom bez základov – bude sa otriasať a rúcať. Ak máte aspoň nejaký čas, tak vytipujte kritické časti v aplikácii a pre tie napíšte jednotkové testy. Je vždy lepšie mať 50 jednotkových testov ako 5 integračných.