Testování a tvorba testovatelného kódu v PHP

V tomto seriálu se podrobně seznámíme s problematikou testování kódu v PHP, a to od úplných začátků po pokročilé metody testování integrace, mockování a další.

Seriál: Testování a tvorba testovatelného kódu v PHP (13 dílů)

  1. Testování a tvorba testovatelného kódu v PHP 13.8.2012
  2. Testování v PHP: Instalace a základy PHPUnit 27.8.2012
  3. Testování v PHP: asserty a constraints 10.9.2012
  4. Testování v PHP: praktický příklad 1.10.2012
  5. Testování v PHP: anotace 8.10.2012
  6. Testování v PHP: odstiňujeme závislosti 22.10.2012
  7. Testování v PHP: odstiňujeme závislosti II. 5.11.2012
  8. Testování v PHP: testy integrace s databází 19.11.2012
  9. Testování v PHP: testy integrace s databází II. 3.12.2012
  10. Testování v PHP: řízení běhu pomocí parametrů 7.1.2013
  11. Testování v PHP: XML konfigurace PHPUnit 21.1.2013
  12. Testování v PHP: tvorba testovatelného kódu 18.2.2013
  13. Testování v PHP: tvorba testovatelného kódu II. 11.3.2013

Proč vlastně testovat?

Odpověď je nasnadě – kód píší lidé a lidé chybují. Jsem si vědom toho, že touto větou jsem se jistě citelně dotkl ega některých čtenářů, ale berte to tak, je to pravda. Ano i vy, i kolega sedící vedle vás, za vámi, před vámi, všichni chybujeme. Příčin chybování je obrovské množství. Jsme unaveni, nemáme dobrou náladu, někdo nás vyrušil, chvátáme domů, přecenili jsme svoje síly, šéf či kolega nás vytočil, atd. atp. Dovolím si tvrdit, že počet všech možných situací, které mohou vést k chybě v kódu, se limitně blíží nekonečnu.

Bohužel i přesto, že s předešlým odstavcem drtivá většina (nejen) programátorů souhlasí, je občas obhajoba přínosu testování podobná boji s větrnými mlýny. Je to škoda, protože tato diskuse by měla znít spíše opačně – ti, kdo se testování svého kódu nevěnují, by měli věrohodně obhajovat proč tak činí.

Proč netestovat?

Za celou dobu, co se v PHP zabývám testováním a vůbec tvorbou testovatelného kódu, jsem posbíral několik zajímavých anti-testing argumentů:

1) žere to moc času…

… který můžu raději věnovat psaní kódu. Toto je asi nejčastější z nich a zaznívá tak půl na půl z úst programátorů a managerů. Těm prvním „se to nechce psát“, ti druzí nechtějí čas, který zabere psaní testů, platit. Ano, tvorba testů zabírá určitý čas, ale tento čas je investicí, která se zatraceně vyplácí. A tím nemyslím jen fakt, že veškeré chyby jsou objeveny a opraveny včas, často ještě před prezentací produktu zákazníkovi. Myslím tím i to, že testy jsou jakýmsi průvodcem tvorby kvalitnějšího kódu. Čím více času investujete do psaní testů, tím méně potom budete potřebovat na refaktoring, který si vyžádá nějaká změna.

2) ještě na to nebyl čas

Celou větou – my víme, že je to správné, ale zatím jsme se k tomu nedostali. Touto větou se budete hájit tak dlouho, než si uděláte opravdu velkou ostudu. A jen ostuda to bude v tom lepším případě. I když může vyústit v diskreditaci firmy, pro kterou pracujete. Nedokážu si představit, jak původní větu přetavit ve smysluplnou omluvu faktu, že účetní data e-shopu, který jste programovali, je možné kompletně vyhodit. Finanční úřad nepatří mezi ty chápavé a laskavé.

3) kód mé aplikace je příliš složitý

Snadná odpověď – pak je ten kód špatný. Dobrý kód = testovatelný kód. Zjistíte to velmi záhy, když budete potřebovat provést nějakou netriviální změnu. Ego „příliš složitého“ kódu pro testování bude rázem pryč a vy se budete potit při rozplétání nekonečné pavučiny závislostí.

4) snad vím, co dělám, ne?

Pozor, vstupujete do nebezpečné skupiny programátorů „všeználků“. Toto si ani nezaslouží komentář. Pokud máte v týmu podobně světového člena, zbavte se ho, o jeho výtvory se pak budete muset starat sami.

5) je to nuda

Věřte, že není. Nejen, že budete nuceni občas překonávat skutečně záludné překážky, ale jakmile si testování zažijete, záhy zjistíte, že díky němu píšete lepší kód. Jestli vám ani jedno nic neříká, pak pracujete ve špatném oboru.

Určitě zaslechnete i mnohé další anti-argumenty, jednou společnou radou proti nim by mohlo být – řiďte se vlastním úsudkem a vlastním svědomím. Pokud se rozhodnete pro cestu testovatelného kódu, nebudete litovat.

Druhy chyb

Tak jako každé rozsáhlejší téma, i testování vyžaduje určité teoretické znalosti, o které se v budoucnu budete moci opřít. Tento, úvodní, díl seriálu bych proto chtěl věnovat výtahu toho, co považuji okolo testování za nejdůležitější. Nejprve se pojďme podívat s jakými protivníky budeme mít tu čest.

Syntaktické chyby

Neboli prohřešky proti syntaxi jazyka – chybějící středník, závorka, … Tyto chyby jsou jedny z „nejhodnějších“, protože vás na ně upozorní už kompilátor a díky tomu nepůjde kód vůbec spustit. Tudíž nebude moci napáchat žádné škody.

        echo "foo"
        echo "bar";

Sémantické chyby

Prohřešky proti sémantice jazyka. To je například špatné pořadí parametrů, špatný typ parametru apod. Tady už je situace horší. Ve většině případů nás na podobné chyby upozorní nějaká varovná zpráva nebo upozornění za běhu, ale to už bývá zoufale pozdě. Nehledě na to, že můžete mít zobrazování těchto zpráv potlačeno. Tyto chyby už mohou napáchat opravdu velké škody.

        $joinedValues = "one, two, three";
        $separateValues = explode($joinedValues);

Logické chyby

Jedny z nejhůře odhalitelných chyb, na které ve velké spoustě případů nebudete nijak upozorněni a jejich řádění může mít fatální následky. Patří sem chyby v cyklech (nedostatečný nebo přílišný počet iterací), chybné používání operátorů, neúmyslné přetypování proměnných a mnohé další.

        $values = array(1, 2, 3, 4, 5);
        for ($i = 1; $i < count($values); $i++) {
            echo $values[$i];
        }

Pomineme-li syntaktické chyby, které jsou nejsnáze odhalitelné, na oba nebezpečné druhy chyb bychom měli být dobře připraveni a v ideálním případě být schopni je odhalit ještě před ostrým spuštěním kódu. A to je přesně ta chvíle, kdy na scénu vstupují naši nejlepší kamarádi jménem testy, které nás na podezřelý kód včas upozorní. Pokud ještě navíc přeruší proces nasazení kódu do produkčního prostředí, tak jen lépe!

Úrovně testování

Jednotkové testy

Nejnižší možná úroveň, zde testujeme izolovaně a samostatně jednotlivé třídy. Na této úrovni nás zajímají především správné návratové hodnoty metod v závislosti na vstupních parametrech. Při jednotkovém testování bychom se měli snažit o maximální izolaci testované jednotky, měli bychom se vyvarovat:

  • volání metod jiných skutečných tříd
  • přistupování k síti (internet, intranet)
  • používání databází
  • používání filesystému
  • atd…

Pro simulaci těchto činností se používají falešné objekty, které nevykonávají žádnou skutečnou činnost, pouze splňují nějaké požadované rozhraní. Tyto objekty se označují jako „mocks“ nebo „stubs“ (příp. ještě „fakes“) a bude o nich řeč později.

Integrační testy

Na této úrovni testujeme, jak spolu naše třídy nebo moduly navzájem spolupracují, testujeme, zda jeden modul splňuje požadavky jiného. Oproti jednotkovým testům zde už můžeme používat databázi, filesystem apod. protože nám jde především o testování spolupráce. Jako příklad integračního testu si můžeme představit test, který nám ověřuje funkčnost nového modulu, který pracuje s CouchDB namísto původního MySQL.

Funkční testy, akceptační testy

Nejvyšší úroveň testování, která se mnohdy odehrává na úrovni GUI, testujeme korektnost chování celé aplikace. Funkční testy jsou většinou automatizovány pomocí nástrojů jako je Selenium, Squish a jiné, akceptační testy mohou být i manuální a provádí je produktový manažer nebo zástupce zákazníka.

Regresní testy

Regresní testy nejsou samostatnou úrovní testování. Důvod je prostý – regresní testy mohou prostupovat všemi úrovněmi testování. Jejich významem je testování úspěšnosti opravy nalezené chyby a hlavně – její neopakovatelnosti. Pokud nalezneme v kódu chybu, pak v ideálním případě bychom měli nejprve napsat test (regresní test), který bude umět chybu vyvolat a teprve potom chybu opravit. Jen tak budeme mít jistotu, že chyba je opravena správně. Kromě toho nám regresní test bude hlídat, že si chybu do kódu nezavlečeme znovu (např. jinou úpravou nebo opravou jiné chyby).

Metody testování

Kromě úrovní testování existují i metody jak testovat, resp. jak přistupovat k testování.

Blackbox testing

Jak už název napovídá, testujeme jakoby černou skříňku. Známe pouze veřejné rozhraní této skříňky a ověřujeme, zda dělá to, co má. Zdrojový kód neznáme. Tato metoda se hodí na testování od úrovně integračních testů výše. Na unit testy není moc vhodná, protože bez znalosti zdrojového kódu nebudeme schopni metody pokrýt dostatečným množstvím testovacích případů.

Whitebox testing

Testujeme přímo kód třídy nebo konkrétní metody. Pokud nepostupujeme metodou Test-driven development (nejprve píšeme testy a kód až následně), tak píšeme testy „na míru“ kódu, tzn. snažíme se o co nejlepší pokrytí kódu testy. Už z předešlé věty je jasná největší nevýhoda této metody („pasujeme“ testy na kód, takže můžeme leccos přehlédnout), ale i přesto bych ji pro unit testy doporučil více než blackbox testing, protože při znalosti kódu jsme schopni vytvořit mnohem více testovacích případů, protože jednoduše vidíme, co metoda dělá.

Pravidla pro testy

Jak se říká – to nejlepší na konec – poslední důležitou kapitolou, kterou bych v tomto úvodním díle rád uvedl, jsou pravidla pro psaní vlastních testů. Tato sada pravidel má pět částí a souhrnně se označuje jako „pravidlo F.I.R.S.T.“ a měla by pro vás být alfou a omegou. Jak si totiž ukážeme později, na mnohá další pravidla a principy dobrého návrhu přijdeme díky této pětici víceméně sami.

Fast – rychlé

Testy by měly být rychlé. Budou-li vaše testy trvat dlouho, pak se vám ani dalším vývojářům nebude chtít je spouštět. A pokud je nebudete spouštět, pak vám nebudou k ničemu.

Independent – nezávislé

Testy by měly být nezávislé – nejen samy na sobě, ale také na prostředí, ve kterém jsou spouštěny. Nesmí dojít k situaci typu: test A nastaví podmínky pro test B, jinak test B neprojde. Závislost testů také může způsobit lavinu „failů“, ze které bude poměrně složité určit prvotní příčinu selhání.

Repeatable – opakovatelné

Stejně jako nezávislé, tak by testy měly být i opakovatelné. A opět – měly by být opakovatelné v jakémkoli prostředí. Test, který dvakrát projde a potřetí selže, je k ničemu.

Self-validating – samovyhodnocující

Za tímto pravidlem se skrývá prostá myšlenka – test by měl buď projít nebo selhat. Žádné romány v konzoli nebo v logu, nad kterými budete trávit hodiny, abyste stanovili, zda test prošel nebo ne. Buď OK nebo FAIL, nic jiného.

Timely – aktuální

Testy bychom měli psát (když ne ještě před vlastním kódem) co nejdříve po napsání ostrého kódu. Jen tak budeme schopni „stíhat“ sledovat, co se v kódu odehrává. Psát testy dodatečně s odstupem několika měsíců opravdu není nijak příjemná práce, nehledě na výslednou kvalitu testů.

Ač tato pětice pokrývá většinu potenciálních problémů, dovolil bych si ji ještě rozšířit o vlastní doporučení:

Krátké

Testy, konkrétně jednotlivé testovací metody, by měly být co nejkratší. Jednak se nikomu nebude chtít studovat test zabírající desítky řádek, kromě toho, jsme-li nuceni vytvořit test, kde desítky řádek slouží k nastavení prostředí pro běh testu, pak zaručeně porušujeme nějaké pravidlo dobrého návrhu nebo dobrého testu (viz. F.I.R.S.T. výše). Puristé tvrdí, že testovací metoda by měla obsahovat pouze jeden assert. To je už malinko extrémistická myšlenka – dovolím si tvrdit, že při tomto přístupu velice brzo narazíte na jeden ze dvou skutečně nejsložitějších problémů v IT – pojmenování (druhým je cache-invalidation :-).

Prosté

Testovací metody by neměly obsahovat žádnou řídící logiku. Mám na mysli podmínky, cykly, apod. Pokud test obsahuje logiku, pak je vysoká pravděpodobnost, že budeme mít chybu v testu, a to je, jak už jsme si řekli, nejhorší, co se nám může stát.

Na téma psaní dobrých testů by se daly napsat celé obsáhlé knihy, ale tím bychom se dostali daleko mimo rámec tohoto seriálu, který vás má především seznámit se základy testování v PHP. Příště už to bude trochu zábavnější a podíváme se na základy práce s testovacím frameworkem PHPUnit.

Josef Zamrzla pracuje jako nezávislý vývojář. Před tím působil coby software development engineer ve společnosti Skype, programátor ve společnosti LMC s.r.o. (provozovatel pracovních portálů www.jobs.cz a www.prace.cz) nebo teamleader ve společnosti Kasa.cz 

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 34

Přehled komentářů

5o Re: Testování a tvorba testovatelného kódu v PHP
anonym Re: Testování a tvorba testovatelného kódu v PHP
Tharos Díky za článek
Aleš Roubíček Re: Díky za článek
Michal Re: Díky za článek
Michal Re: Díky za článek
Sadam Howad
j3nda Re: Howad
hromadadan Re: Howad
Michal Re: Howad
failer Re: Howad
Sadam Re: Howad
Clary Dodatek
Michal Re: Dodatek
yad Testy by mali byť krátke...
arron Re: Testy by mali byť krátke...
yad Re: Testy by mali byť krátke...
arron Re: Testy by mali byť krátke...
Aleš Roubíček Re: Testy by mali byť krátke...
yad Re: Testy by mali byť krátke...
Aleš Roubíček Re: Testy by mali byť krátke...
yad Re: Testy by mali byť krátke...
arron Re: Testy by mali byť krátke...
Paja pridal bych bod 6 do sekce vymluv
enumag Žádost o vysvětlení
5o Re: Žádost o vysvětlení
enumag Re: Žádost o vysvětlení
arron Re: Žádost o vysvětlení
Clary Re: Žádost o vysvětlení
5o Re: Žádost o vysvětlení
jos Re: Žádost o vysvětlení
enumag Re: Žádost o vysvětlení
jos Re: Žádost o vysvětlení
5o Re: Žádost o vysvětlení
Zdroj: https://www.zdrojak.cz/?p=3693