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.

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ší Most Voted

Odysseus: PewDiePie vydal open-source AI workspace, který běží na vašem vlastním hardwaru

AI
Komentáře: 0
Felix Kjellberg, youtuber se 110 miliony odběratelů, strávil rok učením se programovat a fine-tuningem vlastních AI modelů. Výsledkem je Odysseus – bezplatný, open-source workspace pro práci s umělou inteligencí, který neposílá žádná data do cloudu. Projekt má týden, přes 61 000 hvězdiček na GitHubu a znovu otevírá otázku, komu vlastně patří váš digitální kontext.

Když Git už nestačí: jak izolovat databázový stav pro pokusy AI agentů

Gitová větev vývojářům oddělí kód, ale databáze často zůstává společná. U AI agentů je to slabé místo: rychle spouštějí migrace, mění data a zkoušejí víc cest najednou. Databázová větev jim dá vlastní pracovní prostor, jenže tím práce nekončí. Ještě je potřeba řešit citlivá data, oprávnění, životnost větve i zbytek stavu aplikace.

GitHub vyhrál pohodlím. Stejné pohodlí dnes ztěžuje odchod

GitHub kdysi působil jako přesný opak SourceForge: rychlý, přehledný a přirozený. Dnešní projekt na něm ale často nemá jen kód. Má tam issues, pull requesty, CI, balíčky, bezpečnostní pravidla i AI agenty. Lock-in nevzniká tím, že by nešel odnést Git repozitář, ale tím, že se běžný provoz týmu postupně přesune do jedné platformy.