V tomto článku chcem ukázať niekoľko spôsobov ako mi unit testy šetria čas. Tým nadväzujem na predchádzajúci článok o tom, čo mám rád na unit testoch.
Pozitívna psychológia
Keď viem, že veci fungujú, a že o prípadných chybách sa hneď dozviem, tak som pokojný. Trúfnem si robiť väčšie refaktoringy, ktoré zlepšia kód. A ten sa potom ľahšie číta, chápe a mení. Šetrí to čas.
Naopak, keď je človek v strese, tak robí chyby. Keď má strach, tak ostane pri prvom riešení a nesnaží sa ho zlepšovať. Veď by mohol niečo pokaziť. A to nepotrebuje. Takže kód zostane taký „neupravený” ako bol na začiatku. Lenže takto pri každom ďalšom čítaní strácame čas. Prípadne vnesieme chybu spôsobenú neporozumením.
Porozumenie čo komponent robí
Dobre napísaný test vie výrazne skrátiť čas potrebný po porozumenie čo komponent robí. Dobré meno testu, ktoré popíše ako sa správa komponent, nahradí čítanie veľa riadkov kódu. Tým ušetrím čas, námahu a znížim pravdepodobnosť, že niečo pochopím zle, alebo nepochopím vôbec a vzdám to.
Ak je aj telo testu krátke a jednoduché, máme benefit navyše — príklad, ako sa má komponent používať. Navyše je tento test lepší príklad ako príklad v dokumentácii, lebo určite funguje pre aktuálnu verziu komponentu. Aj toto vie ušetriť čas.
Viem kedy som hotový
Kedy som hotový s úlohou? Keď komponent robí to čo má a nič som nepokazil. Ako to viem overiť? Predsa testovaním.
Unit testami, integračnými testami, ale aj manuálnym testovaním. Všetky 3 sú potrebné, ale potrebný čas rastie zľava doprava nelineárne. Takže s dobrými unit testami sa ku výsledku zvyčajne dopracujem rýchlejšie, ako keď sa spolieham prevažne na manuálne testovanie.
Kvalita
Ak chcem dosiahnuť popisované benefity unit testov, tak tie testy musia byť dobré — čitateľné, krátke a rýchle. Na to musím mať splnenú nutnú podmienku — komponent musí byť testovateľný.
Testovateľnosť komponentu znamená, že komponent nie je natvrdo napojený na svoje „okolie”. Netestovateľnému komponentu sa testy musia prispôsobiť (rozumej sú komplikované a nečitateľné).
Keď je komponent testovateľný, tak „okolie” komponentu je v teste ľahko zameniteľné za primeranú implementáciu, ktorá zjednodušuje testovanie. A tým umožňuje testu, aby zostal jednoduchý a čitateľný.
Nie je to samoúčelný cieľ. Jeho dosiahnutím sa mi otvárajú ďalšie možnosti. Napríklad jednoduchšia možnosť rozširovania očakávanej funkcionality „komponentu”. Úvodzovky som použil preto, že zvyčajne nechcem aby celá očakávaná funkcionalita bola v jednej triede. Každú netriviálnu funkcionalitu chcem rozložiť medzi niekoľko spolupracujúcich komponentov. Tie sú potom poskladané dokopy v unit teste ale aj v systéme. Toto nám umožňuje zameniť ľubovoľný spolupracujúci komponent za iný, a tým dosiahnem aj zmenu v správaní sa systému. Bez toho, aby som musel prepisovať kus existujúceho kódu. A ešte k tomu dopisovať veľa nových testov. Táto myšlienka sa volá Open-closed principle.
Kvalitné testy neobmedzujú zmeny alebo rozširovanie komponentu. Je možné ich rýchlo upraviť na nové požiadavky. Nemusím nič hackovať, ani robiť kompromisy.
Skoro som zabudol pripomenúť, že Test-Driven Development je prirodzený spôsob tvorby testov práve z pohľadu kvality. Testy vytvorené pomocou TDD sú kvalitné a vyvíjaný komponent testovateľný (flexibilný).
Menej chýb
V komplikovanom softvéri sú vždy chyby. Súvisí to s množstvom „pohyblivých dielov” a komplexitou. Jednoducho nie je možné si udržať v hlave celý obraz so všetkými detailami. Čím je softvér väčší, tým viac ciest existuje (počet ciest rastie exponenciálne). Takže aj na otestovanie celku potrebujeme vynaložiť neprimerane veľa úsilia.
Unit testy, tým že sa sústredia na malý zlomok celku, vedia zabezpečiť kvalitné pokrytie testami oveľa ľahšie. Deje sa tak na začiatku exponenciálnej krivky, kde ešte vyzerá lineárne. S lepším pokrytím odchytíme podstatnú časť chýb už počas prvotného vývoja.
Rýchle odhalenie chyby
Rýchlo bežiace testy nemám problém spustiť každú chvíľu — po každej malej zmene kódu. Ak test očervenie, viem okamžite, kde nastala chyba — v tom malom kúsku kódu, čo som práve zmenil. Takú chybu dokážem opraviť za krátky čas.
Ak by som mal na výsledky testov čakať dve minúty, tak radšej ich budem spúšťať len občas. Spravím väčšiu zmenu (viac krokov refaktoringu, alebo viac detailov o novej funkcionalite) a potom ich spustím. Ak testy zlyhajú, tak mi, samozrejme, bude aj dlhšie trvať, kým nájdem chybu. (Toto platí vo všeobecnosti. Samozrejme, že občas nájdem a vyriešim problém na prvý pohľad. Ale toto nie je také bežné ako by som si želal.)
A čo ak testy nespustím vôbec? Napríklad preto, že neexistujú (zjavne som „ušetril čas” potrebný na ich napísanie). Prípadne preto, že som si nevytvoril návyk na ich spúšťanie? V lepšom prípade dostanem email o tom, že testy zlyhali pri builde na servri. V tom horšom, dostanem Jira defekt od testera alebo zákazníka. Oprava takej chyby už môže zabrať niekoľko hodín alebo aj dní.
Záver
V celom texte som používal slovné spojenia „dobre napísaný”, „dobré meno” a podobne. Nie je to samozrejmosť. Na to, aby som takýto stav dosiahol musím vynaložiť úsilie. Ale toto úsilie stojí zato, lebo v dlhodobom horizonte sa táto investícia vráti aj s úrokmi — v podobe ušetreného času.
Samozrejme platí to aj naopak. Ak sa nesnažíme o kvalitné, čitateľné a rýchle testy, tiež sa nám to vráti aj s úrokmi. Ale v takom prípade by nadpis tohto článku znel úplne inak.
„Vďaka mojim dlhoročným skúsenostiam s tvorbou unit testov a refaktoringom pomáham programátorom zmeniť ich postoj z musím na oveľa lepší — baví ma a dokážem.”