Skip to content

Instantly share code, notes, and snippets.

@erenon
Created March 20, 2010 10:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erenon/338587 to your computer and use it in GitHub Desktop.
Save erenon/338587 to your computer and use it in GitHub Desktop.
WL-Testme cikk
/**
* A Weblabor számára egységtesztelés témakörben írt cikk első része
*
* @author erenon
* @alias WL-Testme
* @license New BSD
*
*/
PHP osztályok egységtesztelése
==============================
Minden megírt kódsor után előveszed a böngészőt, hogy megnézd, működik-e a megírt kód? Előfordult már, hogy egy függvényt többféle adattal is tesztelned kellett, minden egyes módosítás után? Mindig ki kellett töltened a 15 elemű űrlapodat, hogy megnézd, működik-e a rekord rögzítése? Ezután mindig manuálisan törölni kellett a teszt adatokat? Ha ezek alapján ráismersz az általános munkastílusodra, itt az idő, hogy megismerkedj az egységteszteléssel. Egy módszer, ami segít jobb minőségű kód előállításában, a hibakeresésben és refaktorizálásban, lecsökkentve a tesztelésre fordított időt.
Ha egy tesztelési folyamatot egynél többször kell végrehajtanunk kézzel, hamar belátjuk, egy olyan feladatot végzünk újra és újra, amit egy gép is meg tudna csinálni helyettünk, gyorsabban, olcsóbban és jobban. Bízzuk hát számítógépünkre a tesztelés feladatát automatizált tesztek segítségével. Ha egy fejlesztés alatt álló komponens tesztelése folyamán figyelembe kell vennünk más komponenseket is, tesztjeink megbízhatatlanok és túlzottan összetettek lesznek. Az egységtesztelés szellemében megírt kódkomponensek tesztelhetőek más komponensek nélkül, így nem csak nagyobb megbízhatóságot, hanem könnyebb újrahasznosíthatóságot elérve.
Az egységtesztelés folyamán ilyen komponenseket (egységeket) tesztelünk előre meghatározott adatokkal és környezettel, más egység bevonása nélkül; méghozzá a kód írása előtt, közben és után, folyamatosan.
Egységtesztelés PHP nyelven
---------------------------
Az egységtesztelés megvalósítására több eszköz[0] is rendelkezésünkre áll, ezek közül a PHPUnit nevűvel fogunk most ismerkedni. A telepítése Linux rendszereken a következőképpen történik:
$ pear channel-discover pear.phpunit.de
$ pear channel-discover pear.symfony-project.com
$ pear install phpunit/PHPUnit
A telepítéshez általában root jogosultság szükséges, Ubuntu rendszereken írjuk a sudo parancsot a pear elé. További telepítési lehetőségekről a PHPUnit manual megfelelő részében[1] olvashatunk. Miután végeztünk, gépeljük a következő parancsot a terminálunkba:
$ phpunit --version
A parancs kimenetként valami hasonlót kell kapnunk - a verzió természetesen változhat:
PHPUnit 3.5.0 by Sebastian Bergmann.
A PHPUnit egy keretrendszert biztosít számunkra, mely megkönnyíti az egységtesztelés folyamatát. Lehetővé teszi tesztdublőrök egyszerű létrehozását, melyek imitálják a tesztelt komponens függőségeinek viselkedését, továbbá jegyzi a végrehajtott teszteket és jelentést készít azokról. Ezen kívűl több hasznos funkcióval is rendelkezik, melyek csak részben érintik az egységtesztelés feladatkörét.
Készítsük el első osztályunkat, melyet majd tesztek alá kívánunk vetni. Valósítson meg matematikai alapműveleteket, például az osztást:
<?php
/**
* arithmetic.php
*/
/**
* Egyszerű matematikai alapműveleteket megvalósító osztály
*/
class Arithmetic
{
/**
* Osztás
*
* @param int $a Osztandó
* @param int $b Osztó
* @return int $a és $b hányadosa
*/
public function division($a, $b)
{
return $a/$b;
}
}
Aki valami zavart érez az osztállyal kapcsolatban, az helyesen teszi. Ezután készítsük el az osztályunkat tesztelő osztályt:
<?php
/*
* arithmetic-test.php
*/
require_once 'arithmetic.php';
/**
* Az Arithmetic osztály műveleteit tesztelő metódusok osztálya
*
* Figyeljük meg, hogy tesztelő osztályunk rendelkezik szülőosztállyal:
* A PHPUnit_Framework_TestCase segítségével a phpunit által futtathatóvá válik osztályunk,
* valamint assert funkciókat is kapunk, melyek a konkrét teszteket végzik.
*/
class ArithmeticTest extends PHPUnit_Framework_TestCase
{
/**
* Az osztás műveletet tesztelő metódus
*/
public function testDivision()
{
$a = 10;
$b = 5;
$return = 2;
$arithmetic = new Arithmetic();
/*
* az assertEquals metódust a PHPUnit_Framework_TestCase osztály definiálja,
* összehasonlítja a két paraméterét, és egyenlőség esetén igazat ad vissza.
* A phpunit az ilyen assert metódusok kimenetéről fog a phpunit jelentést készíteni
*/
$this->assertEquals(
$return,
$arithmetic->division($a, $b)
);
}
}
A két fájlt helyezzük el egy közös mappába, majd a terminálból a mappában állva adjuk ki a következő parancsot:
$ phpunit arithmetic-test.php
Kimenetként valami hasonlót várunk:
PHPUnit 3.5.0 by Sebastian Bergmann.
.
Time: 0 seconds, Memory: 2.75Mb
OK (1 test, 1 assertion)
Ami itt fontos, az az árva pont a fejléc és a statisztikák között. Minden egyes tesztet egy karakter reprezentál a kimenetben, mely a lefutástól függően lehet egy pont siker, vagy F betű sikertelenség esetén, ezentúl felvehet más értékeket[2] is. Az osztályunk átment a teszten, de könnyen beláthatjuk, hogy egy teszt nem teszt. Módosítsuk úgy a teszt osztályunkat, hogy különböző értékekkel tesztelje a kiszemelt metódust.
<?php
/*
* arithmetic-test.php
*/
require_once 'arithmetic.php';
/**
* Az Arithmetic osztály műveleteit tesztelő metódusok osztálya
*/
class ArithmeticTest extends PHPUnit_Framework_TestCase
{
/**
* A testDivision függvénynek szolgáltat teszt adatokat
*
* @see testDivision
*/
public function divisionProvider()
{
return array(
array(10, 5, 2),
array(6, 2, 3),
array(9, 3, 3)
);
}
/**
* Az osztás műveletet tesztelő metódus
*
* @param int $a Osztandó
* @param int $b Osztó
* @return int $a és $b hányadosa
*
* @dataProvider divisionProvider
*/
public function testDivision($a, $b, $return)
{
$arithmetic = new Arithmetic();
$this->assertEquals(
$return,
$arithmetic->division($a, $b)
);
}
}
Figyeljük meg a változásokat: Létrehoztunk egy divisionProvider metódust, ami a teszt adatokat szolgáltatja egy tömbben. A tömb minden egyes eleme egy újabb tömb. A PHPUnit minden egyes tömb esetén meghívja a testDividion függvényt, átadva a tömb elemeit paraméterként. Így az ebben az esetben háromszor fog lefutni. A testDivision függvény paramétereit módosítottuk, hogy fogadni tudja a teszt értékeket, és dokumentációjában feltüntettük a @dataProvider annotációt. Csupán ez a dokumentációs bővítés szükséges a feladat végrehajtásához, de praktikus ennél sokkal bővebben dokumentálni a metódusainkat. A teszt futtatása esetén 3 pontot kell látnunk az eredmény mezőben.
Hibajavítás teszteléssel
------------------------
Az Arithmetic osztályt használva hamar rájöhetünk, hogy valami nincs rendben vele a tesztek ellenére. Ha $b 0-nak adódik, hiba lép fel (0-val osztás). Ez nyilvánvalóan hiba, amit javítanunk kell. Mielőtt azonban nekiesnénk a kódunknak, írjunk egy tesztet. A teszteset most lefuttatva legyen sikertelen, a javítás után pedig sikeres. Így megbizonyosodhatunk arról, hogy a hibajavításunk az igényeknek megfelelő.
Tegyük fel, hogy 0-val osztás esetén a division metódusnak egy Exception_MathError kivételt kell dobnia. Íme a teszteset, ami ezt ellenőrzi:
/**
* Ellenőrzi a 0-val osztás esetén dobandó kivételt
*
* @expectedException Exception_MathError
*/
public function testDivisionByNull()
{
$arithmetic = new Arithmetic();
$arithmetic->division(1, 0);
}
Tesztjeink lefuttatása után a 3 pont mellett egy E betűt is kapunk, mellékelve a hibaüzenettel: Division by zero
Megjegyzés: Tesztünk nem azért bukott meg, mert nem dobott kivételt, hanem mert hiba lépett fel a futás során, amit a phpunit elkapott. Ezért látunk E betűt (Error) F (Fail) helyett.
Miután látjuk a sikertelen tesztet, javítsuk ki a tesztelt metódust:
/**
* Osztás
*
* @param int $a Osztandó
* @param int $b Osztó
* @return int $a és $b hányadosa
* @throws Exception_MathError 0-val osztás esetén
*/
public function division($a, $b)
{
if ($b == 0) {
require_once 'Exception/MathError.php';
throw new Exception_MathError();
}
return $a/$b;
}
Valamint készítsünk egy MathError.php fájlt az Exception mappában, a következő tartalommal:
<?php
class Exception_MathError extends Exception
{}
Megjegyzés: A division függvényünk dokumentációjában feltüntettük a @throws annotációt, figyelmeztetve az esetleges kivételre. Ez nem szükséges a teszteléshez, de ennek ellenére minden esetben illik feltüntetni, a jobb átláthatóság érdekében.
Tesztjeinket lefuttatva már 4 pontot kapunk, jelezve a 4 sikeres tesztet. Ezt a módszert, amikor a hiba javítását egy teszt mentén végezzük, Test-driven bug fixing-nek[3] nevezzük.
Kiegészítés: Amennyiben nem tudjuk azonnal megoldani a problémát, jegyezzünk be egy új hibát a hibakezelő rendszerünkben. Ezután helyezzük el a @ticket annotációt a sikertelen teszt dokumentációjában, például @ticket 35. Ha a teszt sikeres, a PHPUnit lezárja a 35 számú hibát, ha sikertelen, újra megnyitja azt, amennyiben előzőleg lezárásra került. A PHPUnit jelenleg a Trac és GitHub hibakezelő rendszereket támogatja, a funkció igénybevételéhez további konfiguráció szükséges.
Könnyen beláthatjuk, hogy nagy méretű rendszerek esetén egy jól megírt tesztgyűjtemény jelentősen megkönnyíti a fejlesztést. Bár sok időbe telik a megírása, és kiterjedt követelményeket, valamint az osztályok felületének pontos definicióját ígényli. Ilyen mértékű megtervezése komolyabb méretű rendszereknek egyébként is szükséges, így ezek megléte nem lehet akadály. A befektetett idő pedig bőven megtérül a hibakeresés, hibajavítás és refaktorizálás során, nem beszélve arról, hogy a program írása során a sikertelen teszteket látva mindig tudjuk, merre tovább.
Ilyen technikákról (TDD), a tesztek ajánlott szervezéséről, a PHPUnit konfigurációjáról és egységteszt-barát kódról lesz szó a cikk következő részében.
[0]: http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#PHP
[1]: http://www.phpunit.de/manual/current/en/installation.html
[2]: http://www.phpunit.de/manual/current/en/textui.html
[3]: http://xunitpatterns.com/test-driven%20bug%20fixing.html
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment