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ší:
- assertContains($jehla, $kupka) - Zkontroluje, zda $kupka (pole) obsahuje danou hodnotu ($jehla).
- assertCount($ocekavanyPocet, $kolekce) - Zkontroluje, zda má $kolekce $ocekavanyPocet prvků.
- assertFalse($hodnota) - Zkontroluje, zda je hodnota
false
. - assertTrue($hodnota) - Zkontroluje, zda je hodnota
true
. - assertNotEquals($ocekavanaHodnota, $hodnota) - Zkontroluje, zda hodnoty NEjsou stejné. Podobná "Not" metoda je pro většinu assertů, další zde již nebudeme zbytečně zmiňovat.
- assertGreaterThan($ocekavanaHodnota, $hodnota) - Zkontroluje, zda je $hodnota větší než $ocekavanaHodnota.
- assertGreaterThanOrEqual($ocekavanaHodnota, $hodnota) - Zkontroluje, zda je $hodnota větší nebo rovna $ocekavanaHodnota.
- assertLessThan($ocekavanaHodnota, $hodnota) - Zkontroluje, zda je $hodnota menší než $ocekavanaHodnota.
- assertLessThanOrEqual($ocekavanaHodnota, $hodnota) - Zkontroluje, zda je $hodnota menší nebo rovna $ocekavanaHodnota.
- assertNull($hodnota) - Zkontroluje, zda je hodnota
null
. - assertSame($ocekavanaHodnota, $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