Aké možnosti existujú pri testovaní kódu a aké kroky spraviť ešte pred samotným písaním vlastných testov? Formálne vedomosti častokrát nestačia a na rade sú skúsenosti z praxe. V mojom článku budem vychádzať najmä z nich.
Netestuješ, oľutuješ?
Jedna vec je istá, testovanie kódu sa nenaučíte v prvom semestri vysokej školy. Dokonca sa o ňom nedočítate ani na prvých stranách tutoriálov PHP a väčšina php-čkárov, pracujúcich ako freelanceri, sa s ním v praxi stretne minimálne. Dôvod je jednoduchý – kód je vyvíjaný len jedným developerom a logika nie je tak komplikovaná, aby bolo potrebné písať riadky naviac. S pribúdajúcimi triedami, predkami a závislosťami však nastáva problém. A to nie len v prípade, keď je nutné niečo zmeniť, ale aj počas dopisovania metód. Situácia je ešte komplikovanejšia, keď na projekte pracuje viacero programátorov a kód musí medzi sebou spolupracovať a komunikovať.
Na to, aby bolo možné odovzdať ďalšiemu programátorovi metódy, je vhodné vedieť minimálne to, že reálne fungujú a po „zmene premennej“ budú fungovať naďalej. A práve túto situáciu nám riešia testy.
Manuálne testovanie kódu
Najjednoduchšie testovanie, ktoré dokážeme vytvoriť je manuálne simulovanie situácie a zobrazenie výsledku. Čo so situáciou, keď potrebujeme zistiť či naša metóda funguje? Pokiaľ máme metódu, ktorá má 2 vstupné parametre x a y a návratová hodnota je ich súčet, na otestovanie si vytvoríme testovaciu triedu, v nej metódu (akciu) pre test. Ako telo metódy zadefinujeme 2 premenné, vytvoríme novú inštanciu triedy, ktorú chceme otestovať, zavoláme metódu s vloženými parametrami a vypíšeme výsledok. V tomto prípade boli otestované všetky riadky testovanej metódy a správne zobrazený výsledok.
Predstavme si ale situáciu, keď je potrebné metódu rozšíriť o sčítavanie nielen 2 čísel, ale celého vloženého poľa. V tomto prípade už budú potrebné podmienky, overovanie dĺžky poľa, typu hodnôt v poli, atď. Simulovanie situácie v tomto prípade už začína byť komplikovanejšie. Budeme podmienky volať aj v teste? Budeme volať v teste aj cyklus, ktorý prechádza celé pole? Ktorý blok kódu bude otestovaný a ktorý nie?
Toto sú len prvé otázky, ktoré začnú vznikať a nesmierne komplikovať život. V tej chvíli tento spôsob začne byť nepoužiteľný. Práve v tejto situácii nastáva najviac otázok prečo vlastne testovať.
Ľudský faktor
Práve pri ľudskom faktore je potrebné zamyslieť sa, či by ste boli ochotní odovzdať kód, o ktorom neviete či funguje. A ak aj funguje, nie je isté, či ste overili všetky možné prípady, ktoré môžu nastať. A nad „testovaním“ ste strávili viac času ako nad písaním kódu. Práve tu nastáva niekoľko otázok, ktoré si položila väčšina začínajúcich testerov.
Dlhšie píšem testy ako kód. Áno, písanie testov stojí čas. No čím pravidelnejšie testy píšete, tým rýchlejšie ich dostanete do krvi. A následne čas strávený nad hľadaním chýb a debugovaním je nepriamo úmerný k času písania testu.
Test sa na to nedá napísať. Problém je jednoduchý. Váš kód je zlý. Každá metóda má spracovať len svoju časť logiky. Pokiaľ vstupuje príliš veľa parametrov, spracováva sa príliš veľa logiky a je nemožné na to napísať test, chyba je v písaní kódu. Túto problematiku si popíšeme nižšie v TDD.
Nie je na to čas. Pokiaľ tlačí termín odovzdania, klientovi sa ťažko vysvetľuje, že je potrebné dopísať ešte pár testov. No je to o správnom time managemente. Následne to zase ušetrí čas pri pripomienkach a odstraňovaní bugov.
Testujeme
Kedy testovať?
Na túto otázku asi neexistuje jednoznačná odpoveď. Pri maximálnej precíznosti by sa dalo povedať, že vždy. Vyhnete sa tým minimálne:
- syntaktickým chybám (chýbajúca bodkočiarka, zle uzavretá zátvorka a pod.)
- sémantickým chybám (zlé poradie parametrov a pod.)
- logickým chybám (zlý počet cyklov, nesprávna podmienka a pod.)
Čo testovať?
V prvom rade je potrebné si určiť na akej úrovni chceme riešiť testovanie. Či potrebujeme testovať ako celok alebo oddelené časti nezávislé na sebe.
- Unit testy sa starajú o najmenšie bloky kódu a mali by byť úplne nezávislé od zvyšku aplikácie. Zaujíma nás predovšetkým návratová hodnota (true/false) vykonanej metódy. Každá metóda by mala mať napísaný svoj test. Či sa už jedná o test, kde sa bude testovať vstup parametrov, test regulárnych výrazov, logika spracovania, porovnanie s očakávaným výsledkom (same, null, empty, false,...) alebo čokoľvek iné potrebné. Test nesmie v sebe obsahovať žiadnu logiku. Žiadne podmienky, cykly ani nič podobné. Pokiaľ je potrebné do testu niečo také písať, máte chybu buď v kóde alebo v samotnom teste.
- Integračné testy sa starajú o kontrolu integrácie tried/modulov do celku. Najväčší rozdiel oproti jednotkovým testom je, že tu už je povolené používať iné triedy, databázu a pod., keďže nás už zaujíma ako spolupracuje celok.
- Akceptačné testy testujú aplikáciu ako celok.
Ako testovať?
Podľa toho, či sa zaoberáme jednotkovým testom alebo integračným testom je potrebné riešiť aj spôsob testovania. Najčastejší spôsob je používaný pri jednotkových testoch, kde sa počíta s tým, že poznáme zdrojový kód a dokážeme ho teda pokryť testami. Na tento prípad si ale treba dať pozor, lebo vzniká nutkanie prispôsobiť si test tak, aby vyhovoval kódu, z čoho môže vzniknúť zle napísaný test. „A písať testy testov? To je už pre masochistov :).“
Profi za každú cenu?
Áno, ideálne je otestovať 100% kódu, no treba zvážiť, či sa to vzhľadom na charakter zadania alebo výšku rozpočtu aj skutočne oplatí. V týchto prípadoch odporúčame pokryť testami aspoň kritické časti, kde je logika najzložitejšia. Aj v tomto prípade ale platí, že je potrebné dodržiavať niektoré pravidlá označované ako F.I.R.S.T.
- Fast (rýchle) – Testy by mali byť rýchle. Pokiaľ každý test bude trvať 20 sekúnd a testov bude 80, nikomu sa na ne nebude chcieť čakať. Z toho vyplýva aj to, že by mali byť krátke. Testovacia metóda by mala testovať len jednu vec a vracať len jeden assert.
- Independent (nezávislé)– Testy by mali byť nezávislé. Nie len na sebe, ale aj na prostredí. Preto každý test si musí sám nastaviť všetko čo bude potrebovať a nespoliehať sa na iný test alebo iné metódy z aplikácie.
- Repeatable (opakovateľné) – Vždy keď sa test spustí, musí vrátiť ten istý výsledok.
- Self-Verifying (samovyhodnocujúce) – Podstata je v tom, aby sa z testov nestávali logy. Test má vrátiť len true alebo false. Nič medzi tým.
- Timely (aktuálne) – Testy by sa mali písať (v ideálnom prípade) ešte pred začatím písania kódu alebo aspoň tesne po jeho dokončení.
TDD (Test-drivenDevelopment)
V prvom kroku je potrebné definovať funkcionalitu, následne na ňu napísať test, ktorý funkcionalitu overuje a až na konci napísať kód, ktorý to celé spracuje. TDD je teda prístup k vývoju softvéru založený na iteráciách vedúcich k zefektívneniu vývoja. Grafické znázornenie na obrázku nižšie.
TDD je v tomto prípade najvhodnejší proces tvorby aplikácie. Realita je ale častokrát taká, že vyvíjať celý projekt pomocou TDD je problematické.
Výhody (najmä) z dlhodobého hľadiska
Aké je možné z toho vyvodiť závery? Testujte. Testujte všetko čo sa dá a na čo budete mať čas. Možno to pri prvom alebo druhom projekte neuvidíte, no získate výhody do budúcna.
- Naučíte sa písať oveľa kvalitnejší kód. To hovorí za všetko.
- Naučíte sa písať efektívne testy a pri projektoch kde to už bude naozaj nutné, to pôjde jedna radosť.
- Pri opakujúcich sa moduloch budete mať testy prichystané na ďalšie projekty a pri úprave modulu stačí len znova spustiť testy a zistiť či sa niečo zmenilo, resp. kde nastala chyba. Máte ušetrených pár telefonátov od klienta a hodiny nad debugovaním.
Keďže som na začiatku písal, že chcem napísať len teoretický úvod, dávam do pozornosti niektoré ďalšie časti, ktoré odporúčam pozrieť pred prvými praktickými pokusmi.
- Anotácie – skupiny
- MockObjects – alebo takzvané mockovanie, ktoré zabezpečí odstránenie závislostí + využitie abstraktných tried.
- Integračné testovanie – alebo ako na závislosti pri databázach.
A to podstatné, čo by ste si z toho mali odniesť?
- Pokiaľ chcete robiť seriózne projekty, testovanie je nutnosť.
- Čím skôr sa dopracujete ku TDD, tým lepšie. No nie je to cesta na pár týždňov.
- Testovanie vám dá oveľa viac, ako len istotu funkčného kódu.