Testování v PHP: Instalace a základy PHPUnit

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ší. Druhou částí seriálu bude průvodce tvorbou testovatelného kódu v PHP.

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

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.

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: 24

Přehled komentářů

Filips nebezpečí tearDown()
Josef Zamrzla Re: nebezpečí tearDown()
jos Re: nebezpečí tearDown()
Vrtak-CZ Re: nebezpečí tearDown()
jos Re: nebezpečí tearDown()
HosipLan Re: nebezpečí tearDown()
jos Re: nebezpečí tearDown()
jk Re: nebezpečí tearDown()
jos Re: nebezpečí tearDown()
EsoRimer Pěkné, jen to nefunguje :)
Josef Zamrzla Re: Pěkné, jen to nefunguje :)
esorimer Re: Pěkné, jen to nefunguje :)
lenoch Realny priklad
Josef Zamrzla Re: Realny priklad
Clary Re: Realny priklad
lenoch Re: Realny priklad
EsoRimer Re: Realny priklad
Clary Re: Realny priklad
Tomáš Re: Realny priklad
jos Re: Realny priklad
Quark Re: Realny priklad
jos Re: Realny priklad
Ondra K. Jen houšť
EsoRimer Re: Jen houšť
Zdroj: https://www.zdrojak.cz/?p=3699