Lekce 2 - Úvod do unit testů v PHP a instalace PHPUnit
V minulé lekci, Úvod do testování webových aplikací v PHP, jsme si udělali poměrně solidní úvod do problematiky. Také jsme si uvedli v-model, který znázorňuje vztah mezi jednotlivými výstupy fází návrhu a příslušnými testy.
Testy píšeme vždy na základě návrhu, nikoli implementace. Jinými slovy, děláme je na základě očekávané funkčnosti. Ta může být buď přímo od zákazníka (a to v případě akceptačních testů) nebo již od programátora (architekta), kde specifikuje jak se má která metoda chovat. Dnes se budeme věnovat právě těmto testům, kterým říkáme jednotkové (unit testy) a které testují detailní specifikaci aplikace, tedy její třídy.
Pamatujte, že nikdy nepíšeme testy podle toho, jak je něco uvnitř naprogramované! Velmi jednoduše by to mohlo naše myšlení svést jen tím daným způsobem a zapomněli bychom na to, že metodě mohou přijít třeba i jiné vstupy, na které není vůbec připravená. Testování s implementací ve skutečnosti vůbec nesouvisí, vždy testujeme zda je splněno zadání.
Jaké třídy testujeme
Unit testy testují jednotlivé metody ve třídách. Pro jistotu zopakuji, že nemá valný smysl testovat jednoúčelové metody např. v modelech, které např. pouze něco vybírají z databáze. Abychom byli konkrétnější, nemá smysl testovat metodu jako je tato:
public function vlozPolozku($nazev, $cena) { $this->db->dotaz("INSERT INTO polozka (nazev, cena) VALUES (?, ?)", $nazev, $cena); }
Metoda přidává položku do databáze. Typicky je použita jen v nějakém formuláři a pokud by nefungovala, zjistí to akceptační testy, jelikož by se nová položka neobjevila v seznamu. Podobných metod je v aplikaci hodně a zbytečně bychom ztráceli čas pokrýváním něčeho, co snadno pokryjeme v jiných testech.
Unit testy nalezneme nejčastěji u knihoven, tedy nástrojů, které programátor používá na více místech nebo dokonce ve více projektech a měly by být 100% funkční. Možná si vzpomenete, kdy jste použili nějakou knihovnu, staženou např. z GitHubu. Velmi pravděpodobně u ní byly také testy, které se nejčastěji vkládají do složky "tests", která je vedle složky "src" v adresářové struktuře projektu. Pokud např. píšeme aplikaci, ve které často potřebujeme nějaké matematické výpočty, např. faktoriály a další pravděpodobnostní funkce, je samozřejmostí vytvořit si na tyto výpočty knihovnu a je velmi dobrý nápad pokrýt takovou knihovnu testy.
Příklad
Jak asi tušíte, my si podobnou třídu vytvoříme a zkusíme si ji otestovat. Abychom se nezdržovali, vytvořme si pouze jednoduchou kalkulačku, která bude umět:
- sčítat
- odčítat
- násobit
- dělit
Vytvoření projektu
V praxi by ve třídě byly nějaké složitější výpočty, ale tím se
zde zabývat nebudeme. Vytvořte si nový projekt s názvem
kalkulacka
a do něj si přidejte třídu Kalkulacka a
následující implementací:
<?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 zajímavá pouze metoda vydel()
, která vyvolá
výjimku v případě, že dělíme nulou. Výchozí chování PHP je chyba
skriptu, což by v aplikaci uživatel vidět nikdy neměl. Třída by mohla být
klidně i ve jmenném prostoru, v testu by se poté naimportovala standardně
pomocí use
.
PHPUnit
V PHP se unit testy píší nejčastěji v PHPUnit frameworku, který by měl každý PHP programátor znát. Existují samozřejmě alternativní nástroje, např. Nette tester, které ale fungují všechny velmi podobně.

I když bychom mohli PHPUnit nainstalovat samostatně, jak my v kurzu, tak vy u svých aplikací, budeme později potřebovat i další typy testů, alespoň ty akceptační. Na ty je třeba pracovat s dalšími nástroji a instalovat vše zvlášť by dalo poměrně dost práce. Proto PHPUnit nainstalujeme pomocí frameworku Codeception.
Codeception
Codeception je komplexní testovací framework pro PHP, který obsahuje:
- PHPUnit
- Akceptační wrapper pro Selenium
- Další, pro nás nepodstatné testovací frameworky
Můžeme jej nainstalovat buď přes composer, nebo jednoduše stažením jediného souboru .phar. Pokud jste o .phar souborech ještě neslyšeli, jsou to spustitelné archivy s PHP aplikacemi, které lze spouštět např. přes vaše IDE.
Instalace
Já zde využiji první možnost přes .phar soubor, řešení přes composer je pro zájemce níže. Přejdeme na quickstart na adrese http://codeception.com/quickstart a stáhneme soubor codecept.phar, ideálně si jej uložte do složky s dnešním projektem.
Články jsou testovány v Codeception verze 4.1.7. Tuto verzi lze stáhnout zde: https://codeception.com/builds (klepnutím na odkaz Download latest 4.1 Release).
Jelikož testy jsou již pokročilejší téma, použijeme pro něj i pokročilejší IDE - PhpStorm. Samozřejmě můžete testovat i v NetBeans, pokud z nějakého důvodu chcete.
Nyní vytvoříme pro .phar soubor alias, abychom ho mohli jednoduše spouštět z konzole. V aplikačním menu File zvolíme Settings a do vyhledávání nahoře napíšeme "Command Li", což nám otevře nástroj Command Line Tool Support.

Pomocí tlačítka "+" vpravo nahoře přidáme novou položku typu "Custom tool" s viditelností pro projekt. Následně do formuláře vyplníme:
- Tool path:
C:\xampp\php\php.exe codecept.phar
, cestu ke svému PHP interpreteru si případně upravte. Na Linuxu stačí zadat místo cesty jen php. - Alias: test (to je název příkazu, přes který budeme archiv spouštět)

Všechna okna potvrďte.
Instalace přes Composer
Tato pasáž popisuje instalaci Codeception přes Composer. Pokud jej
nepoužíváte, přeskočte ji. Máte-li Composer, Codeception nainstalujete
příkazem composer require "codeception/codeception" --dev
Články jsou testovány v Codeception verze 4.1.7, což byla
poslední verze v době jejich aktualizace. Tuto specifickou verzi lze stáhnout
příkazem: composer require "codeception/codeception:4.1.7"
.
Doporučuji si PhpStorm s Composerem a Codeception propojit, jinak pravděpodobně nebude fungovat napovídání kódu.
IDE by se mělo nastavit automaticky, nicméně raději přikládám screenshoty mého nastavení:



V tomto případě bude nastavená cesta pro příkaz Composeru jako cesta k
batch souboru vygenerovaném přes Composer. Měla by vypadat takto:
cesta_k_projektu\Kalkulacka\vendor\bin\codecept.bat
. V případě
Linuxu použijete soubor bez koncovky .bat
.
Bootstrap
Nyní otevřeme menu Tools -> Run command a do konzole (pozor, neplést s terminálem, ten je v PhpStorm také) vložíme následující kód:
test bootstrap
Tím docílíme vygenerování testů do našeho projektu.
Všimněte si, že se v něm objevila složka tests
, která
obsahuje několik dalších souborů a podsložek. Pro nás bude zatím
důležitá podsložka unit
, do které budeme generovat nové unit
testy. Jelikož testy používají třídy z naší aplikace, potřebují
definovat minimálně autoloader. To se dělá v souborech
_bootstrap.php
, které jsou zde buď zvlášť pro každý typ
testů nebo pro všechny testy.
My si v našem případě vytvoříme soubor _bootstrap.php
ve
složce 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šimněte si, že autoloader vrací false
v případě, že se
mu nepodaří třídu načíst. To je velmi důležité, jelikož to tak po něm
mohou převzít další autoloadery Codeception, které používá na své
soubory.
Může se stát, že ve výchozím nastavení tyto soubory nebudou povoleny.
Zkontrolujte proto raději soubor codeception.yml
, ve složce
nadřazené složce s testy, že obsahuje následující řádky:
bootstrap: _bootstrap.php
Pro úplnost přikládám, jak soubor vypadá v mém případě:
paths: tests: tests output: tests/_output data: tests/_data support: tests/_support envs: tests/_envs actor_suffix: Tester bootstrap: _bootstrap.php extensions: enabled: - Codeception\Extension\RunFailed
Generování testů
Vygenerování nového unit testu provedeme také příkazem v konzoli:
test generate:test unit KalkulackaTest
Název testu se zpravidla sestavuje jako název testované třídy + slovo "Test", v našem případě tedy "KalkulackaTest". Jméno testu si v příkazu vždy upravte podle názvu třídy, kteoru testujete.
Pokud vynecháte na konci názvu slovo "Test", bude automaticky
doplněno. Lze tedy použít i následující příkaz:
test generate:test unit Kalkulacka
a výsledek bude stejný.
Pokud byste někdy potřebovali vygenerovat testy do jiné složky (např.
měli více složek tests
v jednom projektu, jednu ve složce
app
a druhou ve vendor
), specifikujete ji takto:
test --config=vendor/NejakyFrameworkSeSlozkouTests generate:test unit NejakyTest
Parametrem --config
se specifikuje cesta ke
složce, která obsahuje konfigurační soubor codeception.yml
,
daná složka tedy musí obsahovat testy (strukturu vygenerovanou pomocí
bootstrap
příkazu). Následně lze takto generovat a spouštět
testy různých submodulů v systému.
Ve složce unit se nám vygeneroval nový soubor s následujícím kódem:
<?php class KalkulackaTest extends \Codeception\Test\Unit { /** * @var \UnitTester */ protected $tester; protected function _before() { } protected function _after() { } // tests public function testSomeFeature() { } }
Případně můžete mít verzi PHPUnit, která používá jmenné prostory, potom bude dědit z třídy pojmenované jen TestCase a nad ní bude import z příslušného prostoru.
Asi vás nepřekvapí, že je test třídy (scénář) reprezentovaný také
třídou a jednotlivé testy metodami Co je již zajímavější je fakt, že na ni nalezneme již
předpřipravené metody. Ta poslední, začínající na slovo "test", bude
jako každá metoda začínající na "test" automaticky spuštěna. Ty další
2 si vysvětlíme příště.
V další lekci, Testování v PHP - Dokončení unit testů, získáme přehled o jednotlivých assert metodách a naučíme se testovat výjimky.