IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 3 - Testování v PHP - Dokončení unit testů

V minulé lekci, Úvod do unit testů v PHP a instalace PHPUnit, jsme si nainstalovali framework Codeception a vygenerovali svůj první PHPUnit test.

Dnes pokryjeme testy naši jednoduchou třídu, uvedeme si dostupné asserční metody a naše unit testy v PHP dokončíme.

Pokrytí třídy testy

Metody _before() (starší název byl setUp()) a _after() (starší název byl tearDown()) se zavolají před, resp. po každém testu v této třídě. To je pro nás velmi důležité, jelikož podle best practices chceme, aby byly testy nezávislé. Obvykle tedy před každým testem připravujeme znovu to samé prostředí, aby se vzájemně vůbec neovlivňovaly. O dobrých praktikách se zmíníme detailněji později. Do třídy si přidejme atribut $kalkulacka a v metodě _before() v něm vždy vytvořme čerstvě novou kalkulačku pro každý test. Pokud by ji bylo ještě třeba dále nastavovat nebo bylo třeba vytvořit další závislosti, byly by také v této metodě. Metodu testSomeFeature() odstraníme:

class KalkulackaTest extends \Codeception\Test\Unit
{
    /**
     * @var \UnitTester
     */
    protected $tester;

    private $kalkulacka;

    protected function _before()
    {
    $this->kalkulacka = new Kalkulacka();
    }

    protected function _after()
    {
    }
}

Máme vše připraveno k přidávání samotných testů. Jednotlivé metody budou vždy začínat na "test" a budou testovat jednu konkrétní metodu z třídy Kalkulacka, typicky pro několik různých vstupů. Pokud vás napadá proč metody označujeme prefixy, umožňuje nám to vytvořit si i pomocné metody, které můžeme v daném testu využívat a které nebudou pokládány za testy. PhpStorm nám totiž testy (metody začínající na "test") automaticky spustí a vypíše jejich výsledky.

Přidejme následujících 5 metod:

<?php
class KalkulackaTest extends \Codeception\Test\Unit
{
    /**
     * @var \UnitTester
     */
    protected $tester;

    private $kalkulacka;

    protected function _before()
    {
        $this->kalkulacka = new Kalkulacka();
    }

    protected function _after()
    {
    }

    public function testScitani()
    {
        $this->assertEquals(2, $this->kalkulacka->secti(1, 1));
        $this->assertEqualsWithDelta(2/3, $this->kalkulacka->secti(1/3, 1/3), 0.001,'');
    }

    public function testOdcitani()
    {
        $this->assertEquals(0, $this->kalkulacka->odecti(1, 1));
        $this->assertEqualsWithDelta(4.86, $this->kalkulacka->odecti(3.14, -1.72), 0.001,'');
        $this->assertEqualsWithDelta(2/3, $this->kalkulacka->odecti(1/3, -1/3), 0.001,'');
    }

    public function testNasobeni()
    {
        $this->assertEquals(2, $this->kalkulacka->vynasob(1, 2));
        $this->assertEqualsWithDelta(-5.4008, $this->kalkulacka->vynasob(3.14, -1.72), 0.001, '');
        $this->assertEqualsWithDelta(0.111, $this->kalkulacka->vynasob(1 / 3, 1 / 3), 0.001, '');
    }

    public function testDeleni()
    {
        $this->assertEquals(2, $this->kalkulacka->vydel(4, 2));
        $this->assertEqualsWithDelta(-1.826, $this->kalkulacka->vydel(3.14, -1.72), 0.001, '');
        $this->assertEquals(1, $this->kalkulacka->vydel(1/3, 1/3));
    }

    public function testDeleniVyjimka()
    {
        $this->expectException(InvalidArgumentException::class);
        $this->kalkulacka->vydel(2, 0);
    }
}

K porovnávání výstupu metody s očekávanou hodnotou používáme poděděné metody assert(). Dají se volat i staticky, ale my zůstaneme u instančního použití. Nejčastěji asi použijete assertEquals(), která přijímá jako první parametr očekávanou hodnotu a jako druhý parametr hodnotu aktuální. Toto pořadí je dobré dodržovat, jinak budete mít hodnoty ve výsledcích testů opačně. Jak asi víte, desetinná čísla jsou v paměti počítače reprezentována binárně (jak jinak :) ) a to způsobí určitou ztrátu jejich přesnosti a také určité obtíže při jejich porovnávání. Proto musíme v některých případech použít metodu assertEqualsWithDelta(), která přijímá třetí parametr a to je delta, tedy kladná tolerance, o kolik se může očekávaná a aktuální hodnota lišit, aby test stále prošel. Posledním parametrem je chybová hláška, pokud test neproběhne. Většinou ji není důvod zadávat, pokud je test dobře pojmenovaný a z hodnot jednoduše poznáme, který assert se nepovedl.

Všimněte si, že zkoušíme různé vstupy. Sčítání netestujeme jen jako 1 + 1 = 2, ale zkusíme celočíselné, desetinné i negativní vstupy, odděleně, a ověříme výsledky. V některých případech by nás mohla zajímat také maximální hodnota datových typů a podobně.

Poslední test ověřuje, zda metoda vydel() opravdu vyvolá výjimku při nulovém děliteli. Jak vidíte, nemusíme se zatěžovat s try-catch bloky, stačí před metodu přidat volání metody expectException() a uvést zde třídu výjimky, která se očekává. Pokud výjimka nenastane, test selže. Pro testování více případů vyvolání výjimky tímto způsobem by bylo třeba přidat více metod.

Dostupné assert metody

Kromě metody assertEquals() můžeme použít ještě mnoho dalších, určitě se snažte použít tu nejvíce vyhovující metodu, zpřehledňuje to hlášky při selhání testů a samozřejmě i následnou opravu. Seznam assert metod je poměrně vyčerpávající a můžete si jej jednoduše prohlédnout v IDE, zmiňme si tedy jen ty nejdůležitější:

  • assertContain­s($jehla, $kupka) - Zkontroluje, zda $kupka (pole) obsahuje danou hodnotu ($jehla).
  • assertCount($o­cekavanyPocet, $kolekce) - Zkontroluje, zda má $kolekce $ocekavanyPocet prvků.
  • assertFalse($hod­nota) - Zkontroluje, zda je hodnota false.
  • assertTrue($hod­nota) - Zkontroluje, zda je hodnota true.
  • assertNotEqual­s($ocekavanaHod­nota, $hodnota) - Zkontroluje, zda hodnoty NEjsou stejné. Podobná "Not" metoda je pro většinu assertů, další zde již nebudeme zbytečně zmiňovat.
  • assertGreater­Than($ocekava­naHodnota, $hodnota) - Zkontroluje, zda je $hodnota větší než $ocekavanaHodnota.
  • assertGreater­ThanOrEqual($o­cekavanaHodno­ta, $hodnota) - Zkontroluje, zda je $hodnota větší nebo rovna $ocekavanaHodnota.
  • assertLessThan($o­cekavanaHodno­ta, $hodnota) - Zkontroluje, zda je $hodnota menší než $ocekavanaHodnota.
  • assertLessTha­nOrEqual($oce­kavanaHodnota, $hodnota) - Zkontroluje, zda je $hodnota menší nebo rovna $ocekavanaHodnota.
  • assertNull($hod­nota) - Zkontroluje, zda je hodnota null.
  • assertSame($o­cekavanaHodno­ta, $hodnota) - Funguje stejně jako assertEquals(), ale kontroluje i shodu datových typů.

Jsou zde i připravené asserty pro testování atributů, řetězců (např. zda něčím začíná), polí, složek, souborů a XML.

assertThat()

Velmi zajímavá je ještě metoda assertThat(), která umožňuje alternativní přístup k assercím. Např. v Javě (PHPUnit poměrně jasně vychází z JUnit) tento způsob přináší navíc další možnosti kontroly datových typů, v PHP si jej zmiňme spíše jen pro zajímavost a ukažme si, jak by vypadal první assert z našich testů pomocí assertThat(). Připomeňme si původní variantu:

$this->assertEquals(2, $this->kalkulacka->secti(1, 1));

A verze s assertThat():

$this->assertThat(
    $this->kalkulacka->secti(1, 1),
    $this->equalTo(
        2
    )
);

Výhodou je, že zápis vypadá jako anglická věta. Nevýhodou je vyšší objem kódu a rekurzivní zanořování. Pro složitější asserty může být tento způsob výhodný.

Spuštění testů

Testy spustíme příkazem:

php codecept.phar run unit

Uvidíme výsledky, které vypadají nějak takto:

> php codecept.phar run unit
Codeception PHP Testing Framework v4.1.7
Powered by PHPUnit 8.5.8 by Sebastian Bergmann and contributors.
Running with seed:


Unit Tests (5) -----------------------------------------------------------------
+ KalkulackaTest: Scitani (0.01s)
+ KalkulackaTest: Odcitani (0.00s)
+ KalkulackaTest: Nasobeni (0.00s)
+ KalkulackaTest: Deleni (0.00s)
+ KalkulackaTest: Deleni vyjimka (0.00s)
--------------------------------------------------------------------------------


Time: 240 ms, Memory: 10.00 MB

OK (5 tests, 12 assertions)

Process finished with exit code 0.
Execution time: 286 ms.

Pokud vám Codeception hlásí problém s nedostupností příkazu "php", otevřete konfigurační soubor codeception.yml a do sekce settings přidejte hodnotu lint: false.

...
settings:
    colors: false
    memory_limit: 1024M
    lint: false
...

V určitých verzích se jinak může špatně vyhodnotit výstupní stav.

Zkusme si nyní udělat v kalkulačce chybu, např. zakomentujme vyvolávání výjimky při dělení nulou a vraťme vždy hodnotu 1:

public function vydel($a, $b)
{
    //if ($b == 0)
    //  throw new \InvalidArgumentException("Nelze dělit nulou!");
    return 1;
}

A spusťme znovu naše testy:

> php codecept.phar run unit
Codeception PHP Testing Framework v4.1.7
Powered by PHPUnit 8.5.8 by Sebastian Bergmann and contributors.
Running with seed:


Unit Tests (5) -----------------------------------------------------------------
+ KalkulackaTest: Scitani (0.01s)
+ KalkulackaTest: Odcitani (0.00s)
+ KalkulackaTest: Nasobeni (0.00s)
x KalkulackaTest: Deleni (0.00s)
x KalkulackaTest: Deleni vyjimka (0.00s)
--------------------------------------------------------------------------------


Time: 246 ms, Memory: 10.00 MB

There were 2 failures:

---------
1) KalkulackaTest: Deleni
 Test  tests\unit\KalkulackaTest.php:testDeleni
Failed asserting that 1 matches expected 2.
#1  C:\Users\Jan\PhpstormProjects\first.php\tests\unit\KalkulackaTest.php:42
#2  C:\Users\Jan\PhpstormProjects\first.php\codecept.phar:5

---------
2) KalkulackaTest: Deleni vyjimka
 Test  tests\unit\KalkulackaTest.php:testDeleniVyjimka
Failed asserting that exception of type "InvalidArgumentException" is thrown.
#1  C:\Users\Jan\PhpstormProjects\first.php\codecept.phar:5

FAILURES!
Tests: 5, Assertions: 10, Failures: 2.

Process finished with exit code 1.
Execution time: 303 ms.

Vidíme, že je chyba zachycena a jsme na ni upozorněni. Neprošel jak test dělení, tak test vyvolání výjimky. Můžeme kód vrátit zpět do původního stavu.

V příští lekci, PHPUnit DataProvider a BestPractices, si ukážeme vybrané zdrojové kódy zajímavých unit testů komerčních systémů, abyste získali přehled jak testovat složitější situace.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 76x (1.29 MB)
Aplikace je včetně zdrojových kódů v jazyce PHP

 

Předchozí článek
Úvod do unit testů v PHP a instalace PHPUnit
Všechny články v sekci
Testování v PHP
Přeskočit článek
(nedoporučujeme)
PHPUnit DataProvider a BestPractices
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
16 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity