V minulém díle jsme se seznámili s nejnutnějšími teoretickými základy testování, dnes se podíváme na základy používání testovacího frameworku PHPUnit.
Instalace
Pokud již máte PHPUnit nainstalován, pak můžete tuto kapitolu přeskočit a přejít hned k základům jeho používání. PHPUnit je možné nainstalovat hned několika způsoby, z nichž nejjednodušší je asi pomocí PEARu. Jako první je dobré si aktualizovat samotný PEAR. Podotýkám, že všechny aktualizace nebo instalace je nutné provádět jako root.
pear channel-update pear.php.net pear upgrade-all
Poté nastavit správné kanály.
pear channel-discover pear.phpunit.de pear channel-discover components.ez.no pear channel-discover pear.symfony-project.com pear update-channels
Nyní už můžeme nainstalovat samotný PHPUnit.
pear install --alldeps --force phpunit/PHPUnit
Instalace zabere jen chvilku a po jejím dokončení by nám příkaz:
phpunit --version
měl vrátit (dle nainstalované verze) výstup podobný:
PHPUnit 3.6.11 by Sebastian Bergmann
Další možnosti instalace
Pokud nemůžete nebo prostě jen nechcete instalovat PHPUnit pomocí PEARu, pak můžete samozřejmě využít svůj balíčkovací systém:
yum install php-phpunit-PHPUnit apt-get install phpunit
V neposlední řadě je PHPUnit možné jednoduše stáhnout pomocí Gitu:
https://github.com/sebastianbergmann/phpunit/#using-phpunit-from-a-git-checkout
Ani u jednoho „alternativního“ způsobu jsem nenarazil na žádný zásadní problém (jak se občas můžete dočíst). Pokud byste přece jen na nějaký narazili, napište do diskuse pod článkem.
Základy PHPUnit
Framework máme nainstalovaný, pojďme se vrhnout na nějaký první test. Aby byl první příklad alespoň trochu smysluplný, musíme mít co testovat. Proto si vytvoříme jednoduchou třídu Calc, která poskytuje dvě metody: multiply pro násobení a divide pro dělení. Implementace obou metod nechme pro ilustraci prosté, bez ošetření vstupních hodnot.
class Calc { public function multiply($x, $y) { return ($x * $y); } public function divide($x, $y) { return ($x / $y); } }
Testy v PHPUnit jsou samostatné třídy, které dědí (ve většině případů) od PHPUnit_Framework_TestCase. Tyto třídy jsou jakýmisi kontejnery na jednotlivé testovací případy, což jsou metody, jejichž názvy začínají prefixem test. Tento prefix zajistí, že PHPUnit takto pojmenované metody sám zavolá. Pokud z nějakého důvodu nechceme tento prefix použít, pak máme ještě možnost testovací metodu označit anotací @test. Možnosti anotací si ukážeme v příštím díle.
Test case vs. TestCase
Možná jste zaznamenali drobný nesoulad ve významu pojmu „test case“, který by měl spíše označovat každou jednotlivou metodu, tedy testovací případ, a nikoli celou sadu. Na toto si bohužel budete muset zvyknout, jde patrně o historický problém ve jmenné konvenci. Pojmem „test case“ budu v tomto seriálu označovat konkrétní testovací případ, tedy metodu. Třídu dědící od PHPUnit_Framework_TestCase a obsahující testovací případy budu označovat jako sadu testů.
Dobrým zvykem je testovací třídy pojmenovávat s postfixem Test a ukládat do souborů, jejichž názvy také nesou tento postfix a jsou umístěny v analogické struktuře jako zdrojové soubory. První umožní aby PHPUnit sám vyhledal a načetl všechny testy, druhé pak pomůže s udržením přehledu a pořádku v testech. Viz náčrt níže.
src |-- lib |-- |-- Calc.php |-- |-- Math.php test |-- lib |-- |-- CalcTest.php |-- |-- MathTest.php
První test
class CalcTest extends PHPUnit_Framework_TestCase { private $calc; protected function setUp() { $this->calc = new Calc(); } public function testMultiply() { $this->assertEquals(10, $this->calc->multiply(2, 5), "Chyba nasobeni: 2 x 5 != 10"); } public function testDivide() { $this->assertEquals(2, $this->calc->divide(10, 5), "Chyba deleni: 10 / 5 != 2"); } }
První jednoduchá sada testů je na světě. Jak už jsem uváděl výše – název třídy nese postfix Test a dědí od PHPUnit_Framework_TestCase. Naše sada obsahuje dva testovací případy: jeden pro metodu multiply, jeden pro metodu divide. V obou případech používáme asertační metodu assertEquals. Omlouvám se za tento trochu kostrbatý překlad, ale neznám vhodný český ekvivalent. Metoda assertEquals očekává dva povinné parametry: očekávaná hodnota a vypočtená hodnota. Pokud se obě hodnoty rovnají, pak je test vyhodnocen jako pravdivý, v opačném případě jako neplatný. Jako třetí, nepovinný, parametr je možné uvést textovou zprávu, která bude zobrazena na výstup, dojde-li k selhání testu. U pravdivých testů je tato zpráva ignorována.
Spouštění testů
Spuštění testu je snadné, stačí spustit příkaz phpunit s cestou k adresáři s testy jako parametrem. Pokud se nejprve do tohoto adresáře přesune, pak stačí: phpunit . (včetně tečky). Jako výstup můžeme očekávat:
$ phpunit . PHPUnit 3.6.11 by Sebastian Bergmann. .. Time: 0 seconds, Memory: 3.25Mb OK (2 tests, 2 assertions)
Tečky v řádku pod číslem verze označují pravdivé (splněné) test cases. Dojde-li k selhání nějakého test case, pak je místo tečky uvedeno jedno z písmen:
- F = fail. Takto je označen test case, který selhal. S největší pravděpodobností nebyl splněn předpoklad v nějaké asertační metodě. Tedy nesoulad mezi předpokládanou a skutečnou návratovou hodnotou.
- E = error. Toto není selhání testu, ale runtime chyba. Může jít o nezachycenou výjimku, nebo některou z odchytitelných chyb v php (notice, warning, error). PHPUnit interně převádí všechny tyto chyby na výjimky.
- I = incompleted. Takto jsou označeny test cases, kterým jsme sami nastavili příznak nedokončeného. Toto je možné provést zavoláním metody markTestIncomplete($msg) s jedním nepovinným parametrem – zprávou (důvod označení). Po jejím zavolání je ignorován zbytek test case.
- S = skipped. Toto označení je podobné předchozímu, s tím rozdílem, že jako skipped bychom měli označit testy, které nebylo možné v daném prostředí spustit. Například kvůli absenci nějakého rozšíření (gd, pdo, …). Označení se provádí zavoláním metody markTestSkipped($msg) opět s jedním nepovinným parametrem – důvodem přeskočení.
assertEquals vs. assertSame
Asertační metoda assertEquals není jediná, kterou PHPUnit nabízí. Je jich celá řada a detailně se jim bude věnovat příští díl tohoto seriálu. Dnes se podíváme ještě na jednu, a tou je assertSame. Očekává stejné parametry jako assertEquals, rozdíl mezi nimi je stejný jako mezi operátory == a ===. Tedy prosté porovnání vs. striktní porovnání, včetně typové shody. Ve většině případů si vystačíte s assertEquals (stejně jako s operátorem ==), assertSame s výhodou využijete třeba pro porovnání dvou polí, kde záleží na pořadí prvků.
$expected = array(0 => 1, 1 => 2, 2 => 3, 3 => 4); $actual = array(1 => 2, 2 => 3, 0 => 1, 3 => 4); $this->assertEquals($expected, $actual); // true $this->assertSame($expected, $actual); // false
Pojmenování test case metod
Stejně jako assertEquals a assertSame, i ostatní asertační metody PHPUnit mají jako poslední, nepovinný, parametr textovou zprávu, která je zobrazena na výstupu při selhání testu. V praxi se ale spíše než tento parametr využívá postup, kdy se každý test case pojmenovává tak, aby bylo na první pohled jasné, co testuje. Například:
public function testDivisionByZeroThrowsException() {/* ... */}
Jednak je pak z výstupu jasné o jaký problém jde, a kromě toho PHPUnit u některých výstupních formátů umí camel-case názvy převádět na věty. Jde například o formát TestDox.
$ phpunit --testdox . PHPUnit 3.6.11 by Sebastian Bergmann. Calc [ ] Division by zero throws fatal error
Metody setUp a tearDown
Dnes se podíváme ještě na dvě užitečné metody PHPUnit – setUp a tearDown. První z nich jste si mohli všimnout u prvního vzorového testu. Jde o šablonovou metodu, která je frameworkem volána před každým jednotlivým testovacím případem. V praxi se používá pro přípravu prostředí před každým testem, např. instancializaci proměnných, které jsou testovacími případy používány.
PHPUnit nabízí i její opačnou variantu – metodu tearDown, která je volána vždy po dokončení každého testovacího případu. U této metody ale pozor na jedno nebezpečí – pokud dojde během testovacího případu k násilnému ukončení běhu skriptu (např. volání nedefinované funkce), pak tato metoda samozřejmě nebude zavolána! Její používání je tedy vždy třeba dobře zvážit. Určitě není dobrým nápadem na ni spoléhat a umisťovat do ní např. zavření file handlerů apod.
Příště…
To je z dnešních základů vše, jako cvičení si můžete napsat další testovací případy (např. ošetření a test chování metody divide při pokusu o dělení nulou apod.). Příště detailně rozebereme všechny asertační metody, které PHPUnit nabízí a také anotace, které kromě ulehčení práce poskytují i další možnosti nastavení chování frameworku PHPUnit.
Přehled komentářů