Přejít k navigační liště

Zdroják » Různé » Začínáme s PHPUnitem – základní test

Začínáme s PHPUnitem – základní test

Články Různé

Dejte mi půl hodiny a já vás naučím napsat test! Tento text slouží jako základní návod pro účastníky veřejného školení, nicméně by měl pomoci všem vývojářům v PHP, kteří testy ještě pořád nepíší a myslí si, že to je složité nebo zdlouhavé. Mám na vás jediné přání: pište se mnou. Kdo dnes napíše svůj první test, ozvěte se v komentářích článku a vyptejte se, co jste nepochopili, co vám šlo/nešlo.

Instalace

Nejdřív si nainstalujte PEAR. Každý, kdo to s PHP myslí vážně, PEAR má (ostatně zkuste si představit programátora v Rails bez Gemu nebo Linuxáka bez balíčkovacího systému). Přesto pokud PEAR nemáte, instalujte podle návodu na výše odkázané stránce.

Samotná instalace PHPUnitu je primitivní. Do příkazové řádky zadejte:

pear channel-discover pear.phpunit.de
pear channel-discover pear.symfony-project.com
pear install phpunit/PHPUnit

Pokud nepoužíváte Windows, instalace proběhne v pořádku. Pokud je máte, je šance tak padesát na padesát, že budete muset použít alternativní návod. Že se instalace podařila, poznáte tak, že do příkazové řádky napíšete phpunit a dostanete výstup podobný tomuto:

PHPUnit 3.4.12 by Sebastian Bergmann.
Usage: phpunit [switches] UnitTest [UnitTest.php]
       phpunit [switches] <directory>
... (dlouhý výpis všemožných voleb)

Šablona testu

A teď si zopakujeme šablonu z předchozího dílu a trochu si ji postupně rozepíšeme.

Test je třída. Obvykle jednu třídu kódu testujeme jednou až několika třídami testů. Mám-li třídu Article, test pojmenovávám ArticleTest (pokud je jich víc, můžeme třídu pojmenovat třeba ArticleSaveTest, ArticleListTest etc.). Důležité je, aby název souboru končil na Test, jinak PHPUnit nepozná, že je v souboru právě třída testu. Existují sice způsoby, jak to obejít, ale na ně pro příštích několik let v klidu zapomeňte.

Třída testu dědí z PHPUnit_Framework_TestCase. Aby byla tato třída přístupná, musíme před třídu napsat require_once "PHPUnit/Framework.php". Mnohem lepší je ale napsat to jednou do souboru bootstrap.php, který si uložíte do složky s testy a budete ho requirovat ve všech testech. Časem zjistíte, že těch společných úkonů na začátku testů je s rostoucím rozsahem systému stále víc.

Samotné testování funkcionality se provádí ve veřejných metodách, které začínají na slovíčko test. Tedy například testSaveData, testValidate… můžete se orientovat podle názvů metod dané třídy, ale není to žádné pravidlo.

Před každým testem se spouští metoda setUp. V té si většinou vytváříme instanci testovaného kódu, dáváme prostředí do výchozího stavu, který je společný všem testům.

class ArticleTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
    }
    public function testSave()
    {
        // kod testu
    }
}

Pamatujte si, že test nemá „otestovat každou řádku“, ale podstatu té třídy. Tedy to, co dělá, jak změní prostředí a co vrátí. Test musí jít za podstatou, když otestujete tu, o moc víc zatím testovacího kódu psát nemusíte. To přijde časem samo!

Při vývoji testu si většinou vystačíte s velmi snadnou šablonou:

  1. test se nazývá stejně jako třída s připojeným slovem Test na konci
  2. testujte všechny metody mimo setterů a jděte po tom, co se vrátí a co se změní, když nastavíte dané podmínky
  3. v setupu si vytvářejte objekt, který budete testovat
  4. vkládejte nějaký bootstrap a ne přímo PHPUnit/Framework.php, často se Vám bude hodit mít nějaký kód společný (i u poměrně malých aplikací to bývá config).

Asserty

Pro otestování stavu vráceného výsledku (ale i například změny souboru na disku) se používají takzvané asserty. PHPUnit nabízí širokou škálu, my si popíšeme ty nejčastěji používané. Přesto byste si měli pamatovat, že existují i asserty pro testování existence/obsahu souboru a pro testování struktury a obsahu XML.

Nejčastější asserty jsou:

assertTrue($predanaHodnotaKteraMaBytTrue)
assertFalse($predanaHodnotaKteraMaBytFalse)
assertType("ocekavany typ", $predanaHodnota)
assertEquals("ocekavana hodnota", $predanaHodnota)

Použití v testu je následující (předpokládám ještě použití dibi):

class ArticleTest
{
    protected function setUp()
    {
        dibi::query("truncate table articles");
        $this->object = new Article;
    }
    public function testSave()
    {
        // vykonam kod
        $data = array("name"=>"Muj clanek", "content"=>"Lorem");
        // otestuju navratovou hodnotu
        $this->assertTrue($this->object->save($data));
        // otestuju zmenu, ktera mela nastat
        $this->assertEquals("Lorem", dibi::query("select content from articles where name = 'Muj clanek'")->fetchSingle());
    }
}

Spouštění testů

Test lze spustit více způsoby (populární je především podpora v různých IDE), my si teď povíme o základním způsobu v příkazové řádce.

Předpokládejme, že testy máte v rootu projektu ve složce tests (nemusím připomínat, že root projektu není totéž, co document_root). Vejděte do složky projektu. Pokud je phpunit správně nainstalovaný, když napíšete phpunit tests, spustí se testy (soubory končící na Test obsahující třídu končící na Test). Pokud zadáte přímo phpunit tests/ArticleTest.php, spustí se jeden konkrétní test case.

Praktická cvičení

Máme třídu Calculator:

class Calculator
{
    const ZERO_DIVIDER_CODE = 0;
    public function divide($a, $b)
    {
        if (0 == $b)
            throw new InvalidArgumentException("Cannot divide by zero", self::ZERO_DIVIDER_CODE);
        return $a / $b;
    }
}

Cvičení 1:

Napište test, který ověří funkčnost třídy.

Cvičení 2:

Napište si test pro funkci multiply a factorial a pak implementujte kód, který projde testem.

Cvičení 3:

Vytvořte si třídu, která bude serializovat na disk objekty (do souborů) a pak z nich bude zase objekty vytahovat. Vždy napište nejdřív test, pak napište kód.

V případě problémů se ozvěte do komentářů.

Tato série slouží jako základní materiál pro školení testování, které pořádá Internet Info spolu s autorem článku Jiřím Kneslem. Mimo školení testování nabízí Jiří Knesl i školení Zend Frameworku, agilních technik a metodik.

Komentáře

Subscribe
Upozornit na
guest
26 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Honza Marek

Aby se dibi kamarádilo s PHPUnitem, je potřeba zakazovat zálohování statických atributů (a možná i globálních proměnných). Objekty DibiConnection totiž vyhazují při pokusu o serializaci výjimku.

Patrik Votoček

<reklamni-sdeleni>kdysi jsem o instalaci na Windows něco blognul http://www.vrtak-cz.net/instalace-phpunit-na-windows a taky udělal „screencast“ http://www.vrtak-cz.net/screencast-pear-phpunit.</reklamni-sdeleni>

Lukas Rychtecky

Hezky clanek pro zacatecniky a lidi, co tvrdi, ze testy jsou ztrata casu. Jen nesouhlasim s tim, ze neni nutne psat testy pro settry.

Satai

Pro setery bych psal test jen pokud obsahuji nejakou (netrivialni) logiku. Dokud je to jedno prirazeni, tak neni duvod, jak je tam trebas podminka testujici korektnost parametru, tak uz si test zaslouzi. (Ty trivialni pripady stejne budou neprimo otestovany v testech jinych metod. A samozrejme je casto uplne nejlepsi psat imutable tridy, kde zadne setery nebudou.)

Aleš Roubíček

„A samozrejme je casto uplne nejlepsi psat imutable tridy, kde zadne setery
nebudou.“
1. Jak chcete v PHP zajistit neměnnost objektu?
2. Proč myslíte, že je to nejlepší? V jednovláknovém prostředí bez jakékoli konkurence. WTF?

Satai

1. Do PHP runtime moc nevidim, byla to obecna rada. Pokud se bude nekdo snazit, tak afaik nemennost v PHP znici, ale pokud je uzivatel vaseho API rozumny, tak porusovat kontrakt nebude.
2. Otazka dobreho stylu, konkurence je mozna nejvetsi argument pro, ale ne jediny. Pri imutabilite je napriklad zbytecne delat defenzivni kopie, nikdo vam nemuze poskodit data, ktera uz drzite.

Aleš Roubíček

Obecně nelze říct, že immutabilita je nejlepší nebo lepší než mutabilita. Jde to říct jen v konkrétních případech řešení nějakého problému.

Satai

S takovou formulaci bych souhlasil. S dodatkem, ze je lepsi nejdrive hledat imutabilni reseni a to opustit az pokud ma nejake vetsi nevyhody.

Borek Bernard

Hezky napsaný článek, ale začít integračním testem, to chce koule :)

Oldřich Vetešník

Pěkný článek, hlavně díky za postup pro instalaci pear + phpunit (byl jsem trochu líný si to vygooglit :).
Těším se na další.

Aleš Roubíček

Já vím, že kritizovat je mnohem snažší než napsat kvalitní článek o testování. Vlastně si myslím, že na to formát článku ani seriálu nestačí. Tady se opravdu vyplatí koupit si knihu nebo se nechat školit. Ale k věci.
Člověk by z napisu řekl, že když jdeo PHPUnit, tak v články budou nějaké jednotkové testy a ouha, máme tu test integrační (v postatě hlavně testujeme funkčnost dibi frameworku, nikoli našeho objektu).
Úkoly k praktickému cvičení jsou dost zavádějící. Přejdu to, že v ukázce někde používáš konstantu, jinde literál, a že vyhozuješ nevhodnou výjimku. :) Jak chceš prosím otestovat *„funkčnost třídy“*? Třída je pouze předpis, podle kterého se vytvářejí objekty, a ten zádnou funkčnost nemá.
„Napište test“ svádí zase k tomu, napsat jeden test, což jistě nestačí. Ano šlo by to udělat jednou testovací metodou, ale pak takový test nemá žádný význam, protože nám není schopný odhalit, co se vlastně stalo špatného. Víme jen, že něco selhalo, a to zjistíme i při spuštění skriptu na serveru. :)
K třetímu úkolu, správný jednotkový test nemá šahat na žádné IO. IO je totiž poamlé a nestabilní a tak nám nemůže zaručit opakovatelné výsledky, což je základní předpoklad pro dobrou test suite.

Borek Bernard

Přesně tak, zanedbaná terminologie v zájmu čtivosti ještě budiž, ale ty integrační testy jsou úlet.

Jiří Knesl

Aleši, nemám teď bohužel dostatek času, abych odpověděl na všechno. Jen bych rád napsal jednu věc, totiž že se naprosto projevuje fakt, že ty prostě testovat umíš a jednoznačně nejsi cílovka článku.
Člověk, který o testování v životě slyšel jen minimum má jen mizivou představu. Já chci věci zjednodušovat, ne mu ještě naklást před oči další věci, jako je rozlišování mezi jednotkovým testem a integračním. Začátečník má přijít, nasimulovat správné použití metody v testu, test spustit a zatím se nestarat o nic víc (včetně toho, jestli to používá IO, jestli je zvyklý používat literály nebo konstanty). Samozřejmě, že cesta od tohoto bodu k plnohodnotnému testování není zrovna krátká, ale myslím si, že je dostatečně únosná i pro ty méně zkušené.

Aleš Roubíček

Já si myslím, že programátoři nejsou malé děti, abys musel zjednodušovat až tolik. Oni to jistě pochopí i když to bude napsaný krapet správnějš. Nejhorší návyky totiž programátoři získávaj na špatných ukázkách a výukových materiálech.

Jiří Knesl

Aleši, řada vývojářů dodnes píše neobjektově, bez verzovacích systémů, s mizivou představou o tom, co jsou to návrhové vzory. Situaci jsem popsal před půl rokem tady: http://zdrojak.root.cz/clanky/mysql-v-roli-neschemove-databaze/nazory/6431/ Bohužel se toho k lepšímu moc nezměnilo (lépe řečeno nic). Nerad bych, aby to vypadalo, že programátoři jsou hloupí, ale já jsem za ty roky v praxi upustil od všech předsudků o tom, kdo co ví a co lze předpokládat. Ostatně se mi už pod ruce dostal i student informatiky na konci prvního ročníku, který nevěděl o existenci „if“. Programátor, který začne testovat, velmi brzo zjistí, že těch pár článků na zdrojáku mu dá jen první kopanec, ty návyky si musí buď někde načíst (což znamená stovky článků – sotva si někdo udělá návyky ze 3 článků na Zdrojáku), nebo získat od někoho zkušenějšího.

Oldis

je to tak, i kdyz sem na php prisel z c++, kde sem pracoval s ryze objektovosti, zacinal sem s php ve firme, kde bylo vse proceduralni, a html zamichane v php, kdyz sem se zminil o tridach, tak sem byl konfrontovan s jednou tridou, a po tom co sem prohlasil ze v tomto pripade jde spis o simulaci jmeneho prostoru, protoze trida je opet proceduralni, nevyuziva dedicnosti, se mi dostalo jen nechapavych pohledu, a prazdnych vyrazu ve tvarich, a to vcetne velevazeneho pana inzenyra, cerstveho absolventa informatiky, ktery byl autorem te tridy. Za svou nekolika letou praxi v oboru sem byl konfrontovan se spoustou takvych projektu. Takze uz me to ani neprekvapuje. Cili ne jen ze se toho mnoho nezmenilo, ale ono se toho taky mnoho nezmeni. Lidi co jsou schopni myslet a pracovat objektove a na urovni je malo, a jsou v oborech s vyrazne vetsi financni vyteznosti, v php se budou stale pohybovat „zacatecnici“ kteri jednak svoji podhodnocenou a podprumernou praci srazeji ceny projektu na nesmyslne urovne a dlasich mraky fusheru, kteri napriklad umi trochu s photoshopem a nejake to php si taky splacnou.

Aleš Roubíček

Kupodivu na procedurálním kódu ve třídě není nic špatného. Spousta lidí píše třídy, aniž by vůbec dosáhli procedurální mety ,a to je pak hodně špatné.
BTW dědičnost není zrovna nejlepším argumentem pro OOP.

Oldis

Pojem proceduralni sem pouzil proto abych se vyhnul zdlouhavemu popisu faktu, ze ona trida jmenujici se kosik, se starala o login/logou­t/registraci/sa­motnej kosik/objednav­ku/mailovani, nacez v sobe mixovala staticky a nestaticky pristup na zhruba 1500 radkach kodu, ktery se mnohdy az napadne opakoval diky tomu tomu ze nekolik funkci delalo to same jen nad jinymi promennymi, dale spolu s nevhodnym pouzivanim pole, coz je videt hodne casto.
Bez dedicnosti by nebylo OOP.

Aleš Roubíček

Procedurální kód kupodivu není kód odzhora dolu, ale dobře strukturovaný do procedur a funkcí.
Tvrzení, že bez dědičnosti by nebylo OOP je přehnaně silné. Dědičnost je až druhořadým aspektem objektového paradigma. Základním je kompozice a ta by měla být před dědičností preferovaná.

Borek Bernard

No já bych řekl, že základem jsou spíš věci jako zapouzdření, information hiding a podobně. Kompozice vs. dědičnost je spíše téma do praxe.

Aleš Roubíček

Základní jsou identita, zpouzdření a kompozice. Bez kompozice by neměl OO návrh smysl. :) Pěkně probrané je to ve spotu http://blog.vyvojar.cz/pbouda/archive/2008/07/30/fundamenty-objektoveho-paradigmatu.aspx

Jiří Knesl

Když už se zapojujeme do té diskuze o tom, co je vlastně OOP, já si vypomůžu Kentem Beckem: „The class name of an object creates a vocabulary for discussing a design. Indeed, many people have remarked that object design has more in common with language design than with procedural program design. We urge learners (and spend considerable time ourselves while designing) to find just the right set of words to describe our objects, a set that is internally consistent and evocative in the context of the larger design environment.“ (http://c2.com/doc/oopsla89/paper.html)
V tomto směru mi OOP pomáhá popisovat problém přirozenou cestou. Ano, toto je možné v Obj-C, Smalltalku, Selfu. V Javě, C#, PHP se OOP redukuje na nějakou kompozici, posílání zpráv, zapouzdření a to je dost málo, kvůli tomu bych se OOP vlastně vůbec neučil (a to už bych radši dělal funkcionální programování, kdyby bylo OOP jen o těch tisíckrát provařených paradigmatech).

Aleš Roubíček

Mně šlo hlavně o to poukázat, že OOP se nám nesmrskává na dědičnost. Vlastně můžu mít objektový jazyk bez podpory dědičnosti a stále to bude plně objektový jazyk. :)

Borek Bernard

Máš pravdu, jen jsem měl na mysli to, že primárnost nebo sekundárnost pojmů nemusí podle mého souviset s užitečností v praxi (viz tvé „základním je kompozice a ta by měla být před dědičností preferovaná“ – souhlasím, ale IMHO to nevychází z teoretické podstaty OOP).

Michal Sänger

Na windows používám „distribuci“ XAMPP:
http://www.apachefriends.org/en/xampp.html
pear i phpunit tam je, ale starý (2.3.6 asi), takže je nejprve potřeba odinstalovat:

pear uninstall phpunit2

pear uninstall phpunit

a až pak instalovat dle návodu v článku.

Daniel Suchý

Postupoval jsem podle návodu, ale i přesto mi PHPUnit nefungoval. Jakmile jsem napsal příkaz „phpunit“ někam jinam, než mimo složku, ve které se nacházel, tak se nic nestalo.
Bylo potřeba nastavit systémovou proměnou PATH. To se udělalo takto:
Počítač->Vlastnosti->Proměnné prostředí->Systémové proměnné a tam je třeba najít proměnnou s názvem „Path“, editovat jí, přidat středník na konec a za něj cestu ke složce PHP.
Jakmile jsem toto udělal, tak už funguje příkaz „phpunit“ odkudkoliv.
Možná to bylo triviální, ale věřím tomu, že ne každý toto ví a proto doufám, že můj stručný návod pomůže.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.