PHP a unit testy

V okamžiku, kdy začnete vyvíjet aspoň trochu seriozní webové aplikace, pak má jistě smysl uvažovat o nějakém testování, nejlépe automatizovaném.

Co jsou unit testy

Základem by měl být unit testing (testování aplikačních jednotek), tedy v případě objektového programování testování toho, zda každá třída dělá správně to, co by měla. Zjednodušeně řečeno, napíšete si testovací skript a proklepnete jím správnou funkčnost všech metod ve vaší třídě pro různé vstupy, s důrazem na různé „speciální“ případy apod.

Výhody

Testovací skripty pro celý projekt můžete sdružovat do makefilů a pak pouhým make unit_tests rychle zkontrolovat všechny zdrojáky (o tom někdy příště). Při provádění úprav ve zdrojovém kódu se následně můžete rychle přesvědčit, zda jste si nezavlekli novou chybu někam, kde byste to vůbec nečekali.

Unit testy pro PHP

Pro PHP používám knihovnu SimpleTest. Umí toho docela dost, zatím všechny možnosti ani zdaleka nevyužívám. Kdysi jsem zkoušel ještě jednu (tuším, že to byl PhpUnit), ale tato mi připadá použitelnější.

Pokusná třída (Kalkulacka.php)

Jeden komentovaný příklad vydá jistě za tisíc slov :)
/**
 * Pokusná třída, jednoduchá kalkulačka, která umí sčítat a dělit čísla.
 */
class Kalkulacka
{
	/**
	 * Prázdný konstruktor.
	 */
	function Kalkulacka()
	{
	}
	/**
	 * Metoda pro sčítání.
	 *
	 * @param a 1. sčítanec
	 * @param b 2. sčítanec
	 * @return součet a + b
	 */
	function Secti($a, $b)
	{
		return $a + $b;
	}
	/**
	 * Metoda pro dělení.
	 *
	 * @param a dělenec
	 * @param b dělitel
	 * @return a děleno b (nebo false, pokud by mělo dojít k dělení nulou)
	 */
	function Vydel($a, $b)
	{
		if ($b != 0)
		{
			return $a / $b;
		}
		else
		{
			return false;
		}
	}
}

Test pokusné třídy (KalkulackaTests.php)

// načíst knihovnu SimpleTest
require_once 'simpletest/unit_tester.php';
require_once 'simpletest/reporter.php';
// načíst naší testovanou třídu
require_once 'Kalkulacka.php';
/**
 * Třída pro testování kalkulačky.
 */
class KalkulackaTests extends UnitTestCase
{
	/**
	 * Konstruktor.
	 */
	function KalkulackaTests()
	{
 		UnitTestCase::UnitTestCase();
	}
	/**
	 * Test sčítání.
	 * (název metody by měl začínat na test...)
	 */
	function testSecti()
	{
		// vytvořit instanci třídy kalkulačka
		$instance = &new Kalkulacka();
		// otestovat, zda byla instance v pořádku vytvořena
		$this->assertNotNull($instance);
		// otestovat, zda volání Secti(1, 1) vrací správně 2
		$this->assertEqual(2, $instance->Secti(1, 1));
		// otestovat, zda volání Secti(0, 15) vrací správně 15
		$this->assertEqual(15, $instance->Secti(0, 15));
		// ... a mohli bychom pokračovat dle libosti
	}
	/**
	 * Test dělení.
	 * (název metody by měl začínat na test...)
	 */
	function testVydel()
	{
		// vytvořit instanci třídy kalkulačka
		$instance = &new Kalkulacka();
		// otestovat, zda byla instance v pořádku vytvořena
		$this->assertNotNull($instance);
		// otestovat, zda volání Vydel(1, 1) vrací správně 1
		$this->assertEqual(1, $instance->Vydel(1, 1));
		// otestovat, zda volání Vydel(0, 1) vrací správně 0
		$this->assertEqual(0, $instance->Vydel(0, 1));
		// otestovat, zda volání Vydel(15, 0) vrací false (pokus o dělení nulou)
		// a nesmí to spadnout
		$this->assertFalse($instance->Vydel(15, 0));
		// ... a mohli bychom pokračovat dle libosti
	}
}
// založíme nový test suite, tedy kolekci testovacích tříd
$test = &new TestSuite('Test pokusné třídy Kalkulacka:');
// přidáme naši testovací třídu (= test case) do tohoto test suitu
$test->addTestCase(new KalkulackaTests());
// spustíme test suite, jako výstup se použije obyčejný textový report
// vrácená hodnota je předána PHP skriptu pro možné pozdější využití
// (0 = OK, jinak se stala chyba)
exit ($test->run(new TextReporter()) ? 0 : 1);

Výsledek

Po spuštění (php -f KalkulackaTests.php) se dočkáme příznivých výsledků:
Test pokusné třídy Kalkulacka:
OK
Test cases run: 1/1, Passes: 7, Failures: 0, Exceptions: 0
SimpleTest nám sděluje, že testy proběhly a v pořádku bylo 7 kontrol, u žádné nebyla zjištěna chyba a neobjevila se ani jedna PHP výjimka.

Zkoušíme udělat chybu

Jak by to vypadalo, kdybych třeba změnil kód sčítací funkce na odčítání (v třídě Kalkulacka, řádek 22)?
return $a - $b;
Spuštění testů by ukázalo toto, tedy dvě chyby s označením testů a čísel řádků, kde k nim došlo:
Test pokusné třídy Kalkulacka:
1) Equal expectation fails because [Integer: 2] differs from [Integer: 0] by 2
at [KalkulackaTests.php line 34]
in testSecti
in KalkulackaTests
2) Equal expectation fails because [Integer: 15] differs from [Integer: -15] by 30
at [KalkulackaTests.php line 37]
in testSecti
in KalkulackaTests
FAILURES!!!
Test cases run: 1/1, Passes: 5, Failures: 2, Exceptions: 0
Dokumentaci k dalším použitelným kontrolám (asserts) lze najít zde. A příště se budu věnovat tzv. mockům :-)

Reagovat