Přejít k navigační liště

Zdroják » PHP » Testování v PHP: XML konfigurace PHPUnit

Testování v PHP: XML konfigurace PHPUnit

Články ‐ ‐ PHP, Různé ‐

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

V 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.

Komentáře

Odebírat
Upozornit na
guest
0 Komentářů
Nejstarší
Nejnovější Nejvíce hlasů

Spacer.gif a tabulky v tabulkách: Jak se kdysi dělaly webové stránky

Kdo dnes napíše display: grid a za minutu má hotový třísloupcový layout, těžko si představí, jakou akrobacii to stálo před pětadvaceti lety. Webové stránky tehdy držely pohromadě vnořené tabulky, průhledné obrázky o velikosti jednoho pixelu a pevná víra, že to nikdo neotevře v jiném prohlížeči. Pojďme si připomenout, jak to fungovalo, proč to fungovalo a proč jsme s tím museli skončit.

Rust pomohl vrátit JPEG XL do Chromu. Stačí to k úspěchu na webu?

Před čtyřmi lety Google podporu JPEG XL z Chromu odstranil. Teď formát vrací s dekodérem v Rustu, který vznikl na popud vývojářů Firefoxu. AVIF přitom může nabídnout menší soubory a bezeztrátový WebP rychlejší dekódování. JPEG XL má jiné přednosti: velkou fotografii dokáže zobrazovat už během stahování a staré JPEGy zmenší bez další ztráty kvality. Vedle historie jeho návratu se podíváme na výsledky srovnání, bezeztrátovou kompresi i praktické nasazení jednotlivých formátů na webu.