2. diel - Úvod do unit testov v PHP a inštalácia PHPUnit
V minulej lekcii, Úvod do testovania webových aplikácií v PHP , sme si urobili pomerne solídny úvod do problematiky. Tiež sme si uviedli v-model, ktorý znázorňuje vzťah medzi jednotlivými výstupmi fázou návrhu a príslušnými testami.
Testy teda píšeme vždy na základe návrhu, nie implementácie. Inými slovami, robíme je na základe očakávanej funkčnosti. Tá môže byť buď priamo od zákazníka (a to v prípade akceptačných testov) alebo už od programátora (architekta), kde špecifikuje ako sa má ktorá metóda správať. Dnes sa budeme venovať práve týmto testom, ktorým hovoríme jednotkové (unit testy) a ktoré testujú detailnú špecifikáciu aplikácie, teda jej triedy.
Pamätajte, že nikdy nepíšeme testy podľa toho, ako je niečo vnútri naprogramované! Veľmi jednoducho by to mohlo naše myslenie zviesť len tým daným spôsobom a zabudli by sme na to, že metóde môžu prísť napríklad aj iné vstupy, na ktoré nie je vôbec pripravená. Testovanie s implementáciou v skutočnosti vôbec nesúvisí, vždy testujeme či je splnené zadanie.
Aké triedy testujeme
Unit testy testujú jednotlivé metódy v triedach. Pre istotu zopakujem, že nemá veľký zmysel testovať jednoúčelové metódy napr. V modeloch, ktoré napr. Len niečo vyberajú z databázy. Aby sme boli konkrétnejší, nemá zmysel testovať metódu ako je táto:
public function vlozPolozku($nazev, $cena) { $this->db->dotaz("INSERT INTO polozka (nazev, cena) VALUES (?, ?)", $nazev, $cena); }
Metóda pridáva položku do databázy. Typicky je použitá len v nejakom formulári a ak by nefungovala, zistí to akceptačné testy, pretože by sa nová položka neobjavila v zozname. Podobných metód je v aplikácii veľa a zbytočne by sme strácali čas pokrývaním niečoho, čo ľahko pokryjeme v iných testoch.
Unit testy nájdeme najčastejšie u knižníc, teda nástrojov, ktoré programátor používa na viacerých miestach alebo dokonca vo viacerých projektoch a mali by byť 100% funkčný. Možno si spomeniete, kedy ste použili nejakú knižnicu, stiahnutú napr. Z GitHub. Veľmi pravdepodobne u nej boli tiež testy, ktoré sa najčastejšie vkladajú do zložky "tests", ktorá je vedľa zložky "src" v adresárovej štruktúre projektu. Ak napr. Píšeme aplikáciu, v ktorej často potrebujeme nejaké matematické výpočty, napr. Faktoriál a ďalšie pravdepodobnostné funkcie, je samozrejmosťou vytvoriť si na tieto výpočty knižnicu a je veľmi dobrý nápad pokryť takú knižnicu testy.
Príklad
Ako asi tušíte, my si podobnú triedu vytvoríme a skúsime si ju otestovať. Aby sme sa nezdržovali, vytvorme si iba jednoduchú kalkulačku, ktorá bude vedieť:
- sčítať
- odčítať
- násobiť
- deliť
Vytvorenie projektu
V praxi by v triede boli nejaké zložitejšie výpočty, ale tým sa tu
zaoberať nebudeme. Vytvorte si nový projekt s názvom kalkulacka
a do neho si pridajte triedu Kalkulacka a nasledujúce implementácií:
<?php /** * Reprezentuje jednoduchou kalkulačku */ class Kalkulacka { /** * Sečte 2 čísla * @param int|float $a První číslo * @param int|float $b Druhé číslo * @return int|float Součet 2 čísel */ public function secti($a, $b) { return $a + $b; } /** * Odečte 2 čísla * @param int|float $a První číslo * @param int|float $b Druhé číslo * @return int|float Rozdíl 2 čísel */ public function odecti($a, $b) { return $a - $b; } /** * Vynásobí 2 čísla * @param int|float $a První číslo * @param int|float $b Druhé číslo * @return int|float Součin 2 čísel */ public function vynasob($a, $b) { return $a * $b; } /** * Vydělí 2 čísla * @param int|float $a První číslo * @param int|float $b Druhé číslo * @return int|float Podíl 2 čísel */ public function vydel($a, $b) { if ($b == 0) throw new \InvalidArgumentException("Nelze dělit nulou!"); return $a / $b; } }
Na kódu je zaujímavá iba metóda vydel()
, ktorá vyvolá
výnimku v prípade, že delíme nulou. Predvolené správanie PHP je chyba
skriptu, čo by v aplikácii užívateľ vidieť nikdy nemal. Trieda by mohla
byť pokojne aj v mennom priestore, v teste by sa potom importovali štandardne
pomocou use
.
PHPUnit
V PHP sa unit testy píšu najčastejšie v PHPUnit frameworku, ktorý by mal každý PHP programátor poznať. Existujú samozrejme alternatívne nástroje, napr. Nette tester, ktoré ale fungujú všetky veľmi podobne.
Aj keď by sme mohli PHPUnit nainštalovať samostatne, ako my v kurze, tak vy u svojich aplikácií, budeme neskôr potrebovať aj ďalšie typy testov, aspoň tie akceptačné. Na tie je potrebné pracovať s ďalšími nástrojmi a inštalovať všetko zvlášť by dalo pomerne dosť práce. Preto PHPUnit nainštalujeme pomocou frameworku Codeception.
Codeception
Codeception je komplexný testovací framework pre PHP, ktorý obsahuje:
- PHPUnit
- Akceptačný wrapper pre Selenium
- Ďalšie, pre nás nepodstatné testovacie frameworky
Môžeme ho nainštalovať buď cez composer, alebo jednoducho stiahnutím jediného súboru .phar. Ak ste o .phar súboroch ešte nepočuli, sú to spustiteľné archívy s PHP aplikáciami, ktoré možno spúšťať napr. Cez vaše IDE.
Inštalácia
Ja tu využijem prvú možnosť cez .phar súbor, riešenie cez composer je pre záujemcov nižšie. Prejdeme na QUICKSTART na adrese http://codeception.com/quickstart a stiahneme súbor codecept.phar, ideálne si ho uložte do priečinka s dnešným projektom.
Články sú testované v Codeception verzie 2.5.6. Túto verziu možno stiahnuť tu: https://codeception.com/builds (kliknutím na odkaz Download latest 2.5 Release).
Keďže testy sú už pokročilejšie tému, použijeme pre neho i pokročilejších IDE - PhpStorm. Samozrejme môžete testovať aj v NetBeans, ak z nejakého dôvodu chcete.
Teraz vytvoríme pre .phar súbor alias, aby sme ho mohli jednoducho spúšťať z konzoly. V aplikačnom menu File zvolíme Settings a do vyhľadávania hore napíšeme "Command Li", čo nám otvorí nástroj Command Line Tool Support.
Pomocou tlačidla "+" vpravo hore pridáme novú položku typu "Custom tool" s viditeľnosťou pre projekt. Následne do formulára vyplníme:
- Tool path:
C:\xampp\php\php.exe codecept.phar
, cestu k svojmu PHP interpreter si prípadne upravte. Na Linuxe stačí zadať miesto cesty len php. - Alias: test (to je názov príkazu, cez ktorý budeme archív spúšťať)
Všetky okná potvrďte.
Inštalácia cez Composer
Táto pasáž opisuje inštaláciu Codeception cez Composer. Pokiaľ ho
nepoužívate, preskočte ju. Ak máte Composer, Codeception nainštalujete
príkazom composer require "codeception/codeception" --dev
Články sú testované v Codeception verzie 2.5.6, čo bola
posledná verzia v dobe ich publikácie. Túto špecifickú verziu možno
stiahnuť príkazom:
composer require "codeception/codeception:2.5.6"
.
Odporúčam si PhpStorm s Composer a Codeception prepojiť, inak pravdepodobne nebude fungovať napovedanie kódu.
IDE by sa malo nastaviť automaticky, však radšej prikladám screenshoty môjho nastavenia:
V tomto prípade bude nastavená cesta pre príkaz Composer ako cesta k batch
súboru vygenerovanom cez Composer. Mala by vyzerať takto:
cesta_k_projektu\Kalkulacka\vendor\bin\codecept.bat
. V prípade
Linuxu použijete súbor bez koncovky .bat
.
Bootstrap
Teraz otvoríme menu Tools -> Run command a do konzoly (pozor, nepliesť s terminálom, ten je v PhpStorm tiež) vložíme nasledujúci kód:
test bootstrap
Tým docielime vygenerovanie testov do nášho projektu.
Všimnite si, že sa v ňom objavila zložka tests
, ktorá
obsahuje niekoľko ďalších súborov a podpriečinkov. Pre nás bude zatiaľ
dôležitá podzložka unit
, do ktorej budeme generovať nové unit
testy. Keďže testy používajú triedy z našej aplikácie, potrebujú
definovať minimálne autoloader. To sa robí v súboroch
_bootstrap.php
, ktoré sú tu buď zvlášť pre každý typ testov
alebo pre všetky testy.
My si v našom prípade vytvoríme súbor _bootstrap.php
v
priečinku unit
, kde si definujeme jednoduchý autoloader:
<?php function autoloader($trida) { if (!file_exists(__DIR__ . '/../../' . $trida . '.php')) return false; require(__DIR__ . '/../../' . $trida . '.php'); } spl_autoload_register('autoloader');
Všimnite si, že autoloader vracia false
v prípade, že sa mu
nepodarí triedu načítať. To je veľmi dôležité, pretože to tak po ňom
môžu prevziať ďalšie Autoloader Codeception, ktoré používa na svoje
súbory.
Môže sa stať, že v predvolenom nastavení tieto súbory nebudú
povolené. Skontrolujte preto radšej súbor codeception.yml
, v
zložke nadradenej zložke s testami, že obsahuje nasledujúce riadky:
settings: bootstrap: _bootstrap.php
Pre úplnosť prikladám, ako súbor vyzerá v mojom prípade:
paths: tests: tests output: tests/_output data: tests/_data support: tests/_support envs: tests/_envs actor_suffix: Tester settings: bootstrap: _bootstrap.php extensions: enabled: - Codeception\Extension\RunFailed
Generovanie testov
Vygenerovanie nového unit testu prevedieme tiež príkazom v konzole:
test generate:test unit KalkulackaTest
Názov testu sa spravidla zostavuje ako názov testované triedy + slovo "Test", v našom prípade teda "KalkulackaTest". Meno testu si v príkaze vždy upravte podľa názvu triedy, kteoru testujete.
Ak vynecháte na konci názvu slovo "Test", bude automaticky
doplnené. Možno teda použiť aj nasledujúci príkaz:
test generate:test unit Kalkulacka
a výsledok bude rovnaký.
Ak by ste niekedy potrebovali vygenerovať testy do inej zložky (napr. Mali
viac zložiek tests
v jednom projekte, jednu v priečinku
app
a druhú vo vendor
), špecifikujete ju takto:
test --config=vendor/NejakyFrameworkSeSlozkouTests generate:test unit NejakyTest
Parametrom --config
sa špecifikuje cesta k
priečinku, ktorá obsahuje konfiguračný súbor codeception.yml
,
daná zložka teda musí obsahovať testy (štruktúru vygenerovanú pomocou
bootstrap
príkazu). Následne je možné takto generovať a
spúšťať testy rôznych submodulov v systéme.
V priečinku unit sa nám vygeneroval nový súbor s nasledujúcim kódom:
<?php class KalkulackaTest extends \Codeception\Test\Unit { /** * @var \UnitTester */ protected $tester; protected function _before() { } protected function _after() { } // tests public function testSomeFeature() { } }
Prípadne môžete mať verziu PHPUnit, ktorá používa menné priestory, potom bude dediť z triedy pomenované len TestCase a nad ňou bude import z príslušného priestoru.
Asi vás neprekvapí, že je test triedy (scenár) reprezentovaný tiež triedou a jednotlivé testy metódami Čo je už zaujímavejšie je fakt, že na ňu nájdeme už predpripravené metódy. Tá posledná, začínajúce na slovo "test", bude ako každá metóda začínajúce na "test" automaticky spustená. Tie ďalšie 2 si vysvetlíme v nasledujúcej lekcii, Testovanie v PHP - Dokončenie unit testov .