Testování v PHP: XML konfigurace PHPUnit

V posledním díle první části seriálu se podíváme na možnosti XML konfigurace PHPUnit.

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 seriálu jsme si ukázali možnosti řízení běhu PHPUnit pomocí parametrů příkazové řádky. Dnes dokončíme první část našeho seriálu a podíváme se na možnosti XML konfigurace frameworku.

Parametry

Stejně jako je možné řídit běh testů pomocí parametrů příkazové řádky, je možné analogické volby používat i v XML. Význam většiny atributů by nám měl být jasný, jde o „camelCase“ obdobu parametrů, které jsme si ukazovali minule.

<phpunit
    backupGlobals="true"
    backupStaticAttributes="false"
    bootstrap="/path/to/bootstrap.php"
    colors="false"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    forceCoversAnnotation="false"
    printerClass="PHPUnit_TextUI_ResultPrinter"
    printerFile="/path/to/ResultPrinter.php"
    processIsolation="false"
    stopOnError="false"
    stopOnFailure="false"
    stopOnIncomplete="false"
    stopOnSkipped="false"
    testSuiteLoaderClass="PHPUnit_Runner_StandardTestSuiteLoader"
    testSuiteLoaderFile="/path/to/StandardTestSuiteLoader.php"
    strict="false"
    verbose="false">
</phpunit>

Nová je tu jen čtveřice atributů:

  • convertNoticesToExceptions, convertWarningsToExceptions, convertErrorsToExceptions – potlačení defaultního chování frameworku, kdy jsou odchytitelné chyby PHP převáděny na výjimky
  • forceCoversAnnotation – omezení generování code coverage reportu pouze na ty části kódu, které jsou označeny anotací @covers

Sady testů

Možnost organizace testů do logických sad využijeme třeba v případě, kdy potřebujeme spouštět pouze určitou část našich testů. Jednou z možností, jak toho docílit, je používat anotaci @group – pak ale musíme důsledně dodržovat označování testů. Druhou a mnohem jednodušší možností jsou právě „test suites“ neboli sady testů. V konfiguračním souboru si vytvoříme jednotlivé sady a do nich zahrneme požadované soubory s testy nebo celé adresáře. Dodatečně můžeme sady testů omezit na určitou verzi PHP.

<testsuites>
    <testsuite name="FirstTestSuite">
        <directory suffix="Test.php" phpVersion="5.3.0" phpVersionOperator=">=">/srv/project/tests/firstGroup</directory>
        <file phpVersion="5.3.0" phpVersionOperator=">=">/srv/project/tests/secondGroup/FooTest.php</file>
        <exclude>/srv/project/tests/firstGroup/bankAccount</exclude>
    </testsuite>
    <testsuite name="SecondTestSuite">
        <directory>/srv/project/tests/secondGroup</directory>
    </testsuite>
</testsuites>

V ukázce výše jsme si nadefinovali dvě sady testů: FirstTestSuite a SecondTestSuite. Do první sady jsme zahrnuli všechny testy z adresáře /srv/project/tests/firstGroup kromě adresáře /srv/project/tests/firstGrou­p/bankAccount. Budou spuštěny všechny testy ze souborů, jejichž název končí Test.php. Navíc jsme do sady přidali soubor /srv/project/tests/secondGrou­p/FooTest.php. Tato sada testů může být spuštěna pouze v prostředí s PHP ve verzi alespoň 5.3.0.

Druhá sada testů (SecondTestSuite) už je definována jednoduše – zahrnuli jsme do ní všechny testy z adresáře /srv/project/tests/secondGroup Budou prohledány všechny soubory bez ohledu na postfix, sada může být spuštěna v prostředí s jakoukoli verzí PHP, kompatibilní s aktuální verzí PHPUnit.

Už nám zbývá odhalit pouze jediné – jak spustit pouze požadovanou sadu. K tomuto slouží parametr příkazové řádky: –testsuite [pattern].

$ phpunit -c phpunit.xml --testsuite FirstTestSuite

Skupiny testů

Stejně jako pomocí přepínačů příkazové řádky, i v konfiguračním souboru můžeme definovat, které skupiny testů chceme do běhu zahrnout a které nikoli. V tomto případě už jsme odkázáni pouze na anotace @group nebo @author. Neplést prosím se sadami, které jsme si ukazovali výše. Zde se jedná pouze o obdobu přepínačů –group a –exclude-group.

Spuštění pouze těch testů, které jsou zařazeny (označeny anotací @group) do skupiny „unit“:

<groups>
    <include>
        <group>unit</group>
    </include>
</groups>

Spuštění všech testů, kromě těch, které jsou zařazeny (označeny anotací @group) do skupiny „integration“:

<groups>
    <exclude>
        <group>integration</group>
    </exclude>
</groups>

Filtrování souborů pro code coverage

Pomocí této direktivy můžeme ovlivnit, které zdrojové soubory budou přidány do generování reportu pokrytí kódu testy (code coverage) a které nikoli. Využití nalezneme třeba v momentě, kdy nechceme generovat code coverage pro soubory z knihoven třetích stran.

Definice se skládá z dvou částí – blacklist a whitelist. Význam obou by nám měl být zřejmý – v sekci blacklist uvedeme, které adresáře nebo konkrétní soubory si nepřejeme do code coverage zařadit, v sekci whitelist opak – které adresáře nebo konkrétní soubory chceme zařadit do code coverage. V každé ze sekcí je možné pro drobnější definici filtru použít ještě direktivu exclude, kterou říkáme „vše, kromě tohoto“.

Direktiva whitelist má jeden nepovinný parametr: processUncoveredFilesFromWhitelist (defaultní hodnota je true). Je-li nastavena na true, pak soubory, které nebyly zařazeny do code coverage, budou zahrnuty do výpočtu statistik (budou započítány počty jejich řádků).

<filter>
    <blacklist>
        <directory suffix=".php">/srv/project/lib/external</directory>
        <file>/srv/project/lib/loader.php</file>
        <exclude>
            <directory suffix=".php">/srv/project/lib/external/foo</directory>
        </exclude>
    </blacklist>
    <whitelist processUncoveredFilesFromWhitelist="true">
        <directory suffix=".php">/srv/project</directory>
        <exclude>
            <directory suffix=".php">/srv/project/www</directory>
            <file>/srv/project/data/conf.php</file>
        </exclude>
    </whitelist>
</filter>

Výše uvedený příklad zahrne do generování code coverage všechny soubory s příponou .php z adresářů /srv/project a /srv/project/lib/external/foo. Dále jsme manuálně zablokovali soubory /srv/project/lib/loader.php, /srv/project/data/conf.php a celý adresář /srv/project/www.

Logování výsledků

Možnosti nastavení logování výsledků testů jsou téměř shodné s možnostmi, které nabízí parametry příkazové řádky. Navíc jsou jen atributy pro generování HTML code coverage:

  • charset: znaková sada výsledného HTML
  • highlight: zvýraznění syntaxe jazyka
  • lowUpperBound: horní procentuální hranice pokrytí kódu. Pokud je procentuální pokrytí kódu menší, pak je kód označen jako málo pokrytý testy.
  • highLowerBound: spodní procentuální hranice pokrytí kódu. Pokud je procentuální pokrytí kódu větší, pak je kód označen jako hodně pokrytý testy.
<logging>
    <log type="coverage-html" target="/tmp/report" charset="UTF-8"
         highlight="false" lowUpperBound="35" highLowerBound="70"/>
    <log type="coverage-clover" target="/tmp/coverage.xml"/>
    <log type="coverage-php" target="/tmp/coverage.serialized"/>
    <log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/>
    <log type="json" target="/tmp/logfile.json"/>
    <log type="tap" target="/tmp/logfile.tap"/>
    <log type="junit" target="/tmp/logfile.xml" logIncompleteSkipped="false"/>
    <log type="testdox-html" target="/tmp/testdox.html"/>
    <log type="testdox-text" target="/tmp/testdox.txt"/>
</logging>

Nastavení prostředí

V této konfigurační sekci můžeme ovlivňovat nastavení prostředí, ve kterém testy poběží (runtime změny direktiv v php.ini, include_path, …) nebo před-nastavit hodnoty superglobálních polí ($_GET, $_POST, …). Příklad uvedený v oficiální dokumentaci mluví za vše:

<php>
    <includePath>/some/dir</includePath>
    <ini name="foo" value="bar"/>
    <const name="foo" value="bar"/>
    <var name="foo" value="bar"/>
    <env name="foo" value="bar"/>
    <post name="foo" value="bar"/>
    <get name="foo" value="bar"/>
    <cookie name="foo" value="bar"/>
    <server name="foo" value="bar"/>
    <files name="foo" value="bar"/>
    <request name="foo" value="bar"/>
</php>

Výše uvedené nastavení odpovídá (ve stejném pořadí) tomuto zápisu v PHP:

    set_include_path('/some/dir;' . get_include_path());
    ini_set('foo', 'bar');
    define('foo', 'bar');
    $GLOBALS['foo']  = 'bar';
    $_ENV['foo']     = 'bar';
    $_POST['foo']    = 'bar';
    $_GET['foo']     = 'bar';
    $_COOKIE['foo']  = 'bar';
    $_SERVER['foo']  = 'bar';
    $_FILES['foo']   = 'bar';
    $_REQUEST['foo'] = 'bar';

Test listeners

Poslední kapitolou, na kterou se v souvislosti s PHPUnit podíváme, jsou tzv. Test listeners. Do češtiny by se toto označení si dalo přeložit jako „posluchač výsledků testů“, ale zůstaňme raději u původního názvu. Test listeners se používají k odchytávání výsledků („odposlouchávání“) z průběhu testování k jejim dalšímu zpracování.

Dalším zpracováním může být myšleno rozesílání mailem, logování (např. viz. https://github.com/ben­matselby/phpunit-testlistener-mongo – logování do MongoDB), výpočet statistik a další. Ukážeme si vše na příkladu jednoduchého listeneru, který nám bude mailem hlásit selhání některého z testů.

Každý listener musí implementovat rozhraní PHPUnit_Framework_TestListener, které vyžaduje implementaci osmi metod:

  • addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    Je volána při zachycení chyby v testovaném kódu nebo v testu samotném. Chyba je převedena na výjimku, která je předána jako druhý parametr. Třetím parametrem je čas, kdy došlo k zachycení chyby.
  • addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    Je volána při selhání testu. Selhání testu je převedeno na výjimku, která he předána jako druhý parametr.
  • addIncompleteTest(PHPUnit_Fra­mework_Test $test, Exception $e, $time)
    Je volána při nálezu testu, který je označen jako neúplný.
  • addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    Je volána při nálezu testu, který je označen k přeskočení.
  • startTestSuite(PHPUnit_Fra­mework_TestSuite $suite)
    Je volána při spuštění sady testů.
  • endTestSuite(PHPUnit_Frame­work_TestSuite $suite)
    Je volána při ukončení sady testů.
  • startTest(PHPUnit_Framework_Test $test)
    Je volána při spuštění každého test case.
  • endTest(PHPUnit_Framework_Test $test, $time)
    Je volána při ukončení test case.

Náš vzorový test listener bude v průběhu testování sbírat chybové zprávy a po ukončení testování je odešle na zadanou e-mailovou adresu.

class EmailAddressListener implements PHPUnit_Framework_TestListener
{
    private $mailto;
    private $message;

    public function __construct($mailto)
    {
        $this->mailto = $mailto;
    }

    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        $this->message .= "Error in" . $test->getName() . "n";
        $this->message .= "Error message:" . $e->getMessage() . "n";
    }

    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
        $this->message .= "Failure in" . $test->getName() . "n";
        $this->message .= "Error message:" . $e->getMessage() . "n";
    }

    public function startTest(PHPUnit_Framework_Test $test) {}

    public function endTest(PHPUnit_Framework_Test $test, $time) {}

    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) {}

    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) {}

    public function startTestSuite(PHPUnit_Framework_TestSuite $suite) {}

    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        if ($this->message) {
            mail($this->mailto, "Test Failed at " . date("j.n.Y H:i:s"), $this->message);
        }
    }
}

Poslední, co nám zbývá, je test listener připojit k testům. K tomuto účelu je možné použít direktivu listeners:

<listeners>
    <listener class="EmailAddressListener" file="EmailAddressListener.php">
        <arguments>
            <string>tester@mydomain.com</string>
        </arguments>
    </listener>
</listeners>

Příště

To už je z první části seriálu, kde jsme se seznámili s frameworkem PHPUnit, opravdu vše. Ve druhé části seriálu o testování se na celou problematiku podíváme z druhé strany – jak psát kód, který je testovatelný. Vše si ukážeme na příkladu refactoringu špatně navržené třídy do její lepší podoby.

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é.)

Zatím nebyl přidán žádný komentář, buďte první!

Přidat komentář
Zdroj: https://www.zdrojak.cz/?p=3769