PHPUnit a programování řízené testy

Před nedávnem jsem si koupil knížku Programování řízené testy od Kenta Becka, jednoho z členů gangu čtyř. Knížečka je to sice útlá, ale obsahuje poměrně zajímavé myšlenky, rady a návody. Nedalo mi nezkusit si na svém současném projektu v PHP4 něco ze základů XP a programování řízené testy pomocí PHPUnit. Začal jsem tedy psát napřed testy, pak kód a následně jej refaktorovat. Neuvěřitelné se stalo skutečností a PHPUnit mi hned na počátku pomohlo odhalit dvě vcelku zapeklité chyby, nad kterými jsem předtím strávil přibližně dvě hodiny a ani zdaleka se nepřiblížil k jejich odhalení. Celé programování šlo poměrně rychle kupředu a nyní mám funkční řešení důležité části své aplikace, které vypadá poměrně použitelně a solidně.

Unit testy a PHPUnit

Princip testování pomocí Unit testů spočívá zjednodušeně řečeno ve spouštění programátorem napsaných sad testů, které by měly prověřit všechny aspekty testovaných částí kódu. Takovýchto testů je nutno napsat samozřejmě celou řadu a měly by zkoušet především to, povedlo-li se objekt vytvořit, došlo-li k jeho odstranění, je-li správného datového typu, provádí-li správně požadované operace apod. Poměrně zajímavé možnosti přinášejí Unit testy ve spojení s XP, které se řídí zásadou napřed napsat sadu testů a teprve poté implementovat vlastní třídu a její metody.

Architektura xUnit obsahuje hlavní třídu, v jejímž názvu je většinou obsaženo slovo Unit. Tato třída obsahuje metodu run určenou ke spouštění testů. Tato hlavní třída využívá možnosti třídy TestSuite, která umožňuje přidávat a obsluhovat jednotlivé instance testovacích tříd zděděných od třídy TestCase (kompozice) a spouštět je. Vlastní testy našeho kódu pak probíhají v této zděděné třídě, která obsahuje metody pro inicializaci proměnných (setUp()), úklid proměnných (tearDown()) a konečně také metody pro vlastní testování (aserce).

Předchozí popis je samozřejmě jen hrubým náčrtem toho, jak to uvnitř xUnit vypadá. Pokud se chce čtenář dozvědět více podrobností, dovolím si jej odkázat na již zmiňovanou knihu, případně některou z implementací xUnit.

PHPUnit je implementací testovací architektury xUnit napsané pro programovací jazyk PHP. Je to vlastně jakási obdoba v Javě velmi populárního rámce JUnit.Stejně jako JUnit má i PHPUnit možnost výstupu, který ukáže poměr mezi úspěšně provedenými testy a selháním asercí.

Jak tedy co nejlépe využít možností PHPUnit? Popisované použití je určeno pro PHP4 a pro mé účely zatím plně vyhovovalo. Výstup jednotlivých testů bude ve formě přehledné HTML stránky, kterou nám vytvoří PHP skript.

Instalace

PHPUnit je možné stáhnout a nainstalovat buď pomocí PEAR, anebo přímo ze stránek projektu PHPUnit, na kterém jsou k dispozici PHPUnit2 pro PHP5 a PHPUnit pro PHP4. Instalace pomocí PEAR je poměrně jednoduchá.

# pear install --alldeps PHPUnit

Na webhostinzích nebude PEAR nejspíš k dispozici, stačí tedy PHPUnit umístit do vhodného adresáře a následně vložit potřebné soubory.

Testovaná třída

Pro ukázku práce s PHPUnit jsem vytvořil jednoduchou třídu Picture.class.php pro práci s obrázky. O její užitečnosti lze diskutovat, ale jako ukázková třída pro testování postačí.

  1. class Picture {
  2. var $id;
  3. var $title;
  4. var $path;
  5. var $enable;
  6.  
  7. function Picture($input = false)
  8. {
  9. $this->setPictureId($input['picture_id']);
  10. $this->setPictureTitle($input['picture_title']);
  11. $this->setPicturePath($input['picture_path']);
  12. $this->setPictureEnable($input['picture_enable']);
  13. }
  14.  
  15. function getPictureId()
  16. {
  17. return $this->id;
  18. }
  19.  
  20. function setPictureId($input = false)
  21. {
  22. $this->id = is_numeric($input) ? $input : NULL;
  23. }
  24.  
  25. function getPictureTitle()
  26. {
  27. return $this->title;
  28. }
  29.  
  30. function setPictureTitle($input = false)
  31. {
  32. $this->title = is_string($input) ? $input : NULL;
  33. }
  34.  
  35. function getPicturePath()
  36. {
  37. return $this->path;
  38. }
  39.  
  40. function setPicturePath($input = false)
  41. {
  42. $this->path = is_string($input) ? $input : NULL;
  43. }
  44.  
  45. function isPictureEnable()
  46. {
  47. return $this->enable;
  48. }
  49.  
  50. function setPictureEnable($input = false)
  51. {
  52. $this->enable = is_bool($input) ? $input : false;
  53. }
  54. }

Testovací skripty

Pro automatické testy budeme používat název souboru složený z názvu testované třídy a kouzelného slova Test, v našem případě PictureTest.php. Všechny testy si budeme ukládat do adresáře test a budeme je spouštět pomocí skriptu. Tyto "zavedené" konvence nám poměrně jasně zpřehlední, co vlastně budeme testovat a k jakému účelu soubory slouží. Jako první si napíšeme sadu testů, které nám otestují třídu Picture a ukážeme si možnosti jednotlivých asercí.

Deklarace třídy

V deklaraci testovací třídy je nutné zvolit její správné jméno. Pro tento ukázkový příklad, se bude skládat z názvu adresáře ve kterém se testy nacházejí, podtržítka a názvu souboru s testem bez přípony. Pro náš příklad se třída bude jmenovat test_PictureTest. Bez dodržení těchto zásad bychom nemohli automaticky spouštět naše testy. Tato testovací třída bude díky dědičnosti potomkem třídy PHP_TestCase, která je implementací třídy TestCase.

  1. <?php
  2. // Import hlavního souboru testů
  3. require_once 'PHPUnit.php';
  4. // Import testovaných tříd a skriptů
  5. require_once 'Picture.class.php';
  6. // Třída test_PictureTest dědí metody a proměnné
  7. // od PHP implementace třídy TestCase
  8. class test_PictureTest extends PHPUnit_TestCase
  9. {
  10. var $a;
  11. var $b;
  12. var $c;
  13.  

Konstruktor

V konstruktoru pouze předáme název testu rodičovské třídě.

  1. function test_PictureTest($name) {
  2. $this->PHPUnit_TestCase($name);
  3. }
  4.  

setUp()

V metodě setUp() se provádí inicializace proměnných, definovaných v deklaraci testovací třídy. Tyto objekty jsou při každém testu nově vytvářeny a používány.

  1. function setUp() {
  2. $vstup = array('picture_id' => 2363,
  3. 'picture_title' => 'Naše svatební fotka',
  4. 'picture_path' => '/img/foto/svatba-konec_me_svobody.jpg',
  5. 'picture_enable' => true);
  6. $this->a =& new Picture($vstup);
  7. $this->b =& new Picture();
  8. $this->c = array('moje','tvoje','její');
  9. }
  10.