Behat & Gerkin: (ne)jen testování PHP aplikací

Behat je efektivní testovací BDD nástroj pro vývoj php aplikací. Pomocí jazyka Gherkin umí popsat chování aplikace bez nutnosti zabíhat do detailu kódu. Dokáže vytvářet aktuální specifikaci i automatizaci testů. Rádi by jste nahlédli Behatu pod pokličku? Pak je článek určen právě vám.

Chcete ušetřit čas, zvýšit stabilitu, získat aktuální specifikaci a minimalizovat komunikační šum ve vývoji PHP aplikací? Právě Behat může být pro vás dobrým řešením.

Jde o silný PHP testovací framework vnášející BDD (Behavior-driven development) přístup do testování webových aplikací. Je inspirován projektem Cucumber pro Ruby. Od verze 3.0, která právě vyšla, je Behat oficiální PHP implementací Cucumber a stal se součástí velké rodiny BDD nástrojů. K webtestům zpřístupňuje funkcionalitu známých web emulátorů jako Selenium, Sahi, Goutte. Sladkou třešničkou je srozumitelnost zdrojového kódu díky přirozené syntaxi jazyka Gherkin.

Ačkoliv verze 3.x je již na světě, příklady v článku a často i odkazy na dokumentaci jsou použity z verze 2.x. Verze 3.x totiž vyšla právě v těchto dnech a dokumentace zatím není tak obsáhlá.

Instalace

Instalace není složitá. Využít můžete tyto možnosti: Composer, GIT či PHAR archiv. S testovacím nástrojem komunikujeme příkazem $ behat. Protože se jedná o spustitelný soubor, je třeba k němu uvést cestu. Volání pak může vypadat třeba takto $ ./bin/behat.

Než se pustíme do testování, příkazem $ behat --init vygenerujeme adresář features s podsložkou bootstrap obsahující defaultní bootstrap nazvaný FeatureContext. Do features budeme testy lokalizovat.

Jak se testy vytvářejí?

K potřebám názorné ukázky využijeme následující jednoduchou třídu ke zjištění stupně body mass indexu.

class BodyMassIndex
{
    public function __construct($height, $weight)
    {
        $this->height = $height / 100;
        $this->weight = $weight;
    }

    public function calculateBodyMassIndex()
    {
        return $this->bmi = $this->weight / pow($this->height, 2);
    }

    public function getBodyWeightLevel()
    {
        if ($this->bmi < 18.5) {
            return "underweight";
        } elseif ($this->bmi >= 18.5 && $this->bmi <= 25) {
            return "normal";
        } elseif ($this->bmi > 25) {
            return "overweight";
        }
    }
}

Tajemství testů odhalí feature

Jednotlivé testy tvoří soubory s koncovkou .feature o průzračné struktuře. První řádek uvozuje klíčové slovo Feature a pokračuje názvem. Na následujících odsazených formulujeme popis testované vlastnosti. Texty se píší v neprogramovém jazyce.

Feature k naší třídě BodyMassIndex může vypadat následovně:

Feature: Body Mass Index
    Uživatel chce zjistit Body Mass Index

Uvedená sekce není důležitá pro funkčnost testů, ovšem jde o BDD implementaci. Jsme-li v těchto oddílech důslední, zajistíme vhled do aplikační logiky napříč hierarchií vývoje (od byznys vrstev po vývojáře).

Výkonný kód: Scénáře – obousměrně čitelná testovací logika

Do featur umisťujeme testovací scénáře. Jsou uvozeny klíčovým slovem Scenario. Za ním následuje popis v přirozeném jazyce. Měl by vystihovat chování testované vlastnosti v různých podmínkách. Na dalších odsazených řádcích již tvoříme výkonný kód v jazyce Gherkin. Poví vám za mne, co se děje v tomto příkladě.

Scenario: Uživatel ze své z výšky a váhy chce zjistit úroveň BMI
    Given I have body proportions: height "159" cm and weight "48" kg
    When I calculate body mass index
    Then My weight is "normal"

Jazyk Gherkin, aneb samovysvětlující programový kód

Šetřete náklady! Napsat profesionální test v tomto jazyce nevyžaduje hardcorového programátora. Zvládne to i méně zkušený! Gherkin náleží ke skupině BusinessReadableDSL jazyků. Umožňuje současně psát dokumentaci projektu i automatizované testy. Jazyk vystihuje chování aplikace bez nutnosti zacházet do detailu kódu. Je řádkově orientovaný. Řádky uvozuje některé z následujících klíčových slov: Given, When, Then, But, And. Jsou vyhodnocovány rovnocenně, slouží však k zachycení logiky testovaného kódu.

BDD vláknem protkané testy

Feature je místem porozumění vývojářů i ostatních v projektu zúčastněných. Stává se příběhem aplikace. Směrem od shora dolů je dobře čitelná. Na popisy v neprogramovém jazyce dobře navazují kroky scénářů v bussines-readable Gherkinu. Feature se zároveň stávají dobrým řešením, jak udržovat aktuální specifikaci. BDD odstraňuje bariéry v celém vývojovém procesu.

Jak oživit zápis Gherkinu ze scénářů? Šém není zapotřebí

Gherkin je vyhodnocován po jednotlivých krocích (řádcích). Nehledejme v nich nic jiného než PHP metodu s anotací v komentáři. PHP určuje funkcionalitu, anotaci uvedeme jako řádek scénáře. Vlastní funkcionalitu programujeme ve třídě nejčastěji nazvané FeatureContext.php. Jde o potomka jednoho z Contextů poskytovaného Behatem. V našem případě podědíme BehatContext.

class FeatureContext extends Behat\Behat\Context\BehatContext
{

    private $bmi;

    /**
     * @Given /^I have body proportions: height "(\d+)" cm and weight "(\d+)" kg$/
     */
    public function iHaveBodyProportions($height, $weight)
    {
        $this->bmi = new BodyMassIndex($height, $weight);
    }

    /**
     * @When I calculate body mass index
     */
    public function iCalculateBMI()
    {
        $this->bmi->calculateBodyMassIndex();
    }

    /**
     * @Then /^My weight is "([^"]*)"$/
     */
    public function myWeightIs($weight)
    {
        $bmiWeight = $this->bmi->getBodyWeightLevel();

        if (!strcmp($weight, $bmiWeight)) {

        }
    }
}

Získejte silného spojence pro webtesty

Již jsme dokázali otestovat třídu, ovšem většina PHP aplikací navenek vystavuje HTML. Nyní si nastíníme, jak otestovat obsah webového prohlížeče.

Behat k těmto účelům používá akceptační testovací framework Mink. Interakci mezi prohlížečem a aplikací simuluje pomocí těchto driverů: Goutte, Sahi, Zombie, Selenium, Selenium2. K prvkům webové stránky můžete přistupovat pomocí množství selektorů a node elementů. Použijete-li driver podporující Javascript, otestujete s Minkem i ten.

Webtest jako po másle

Nebudeme chodit kolem horké kaše a vezmeme si na mušku server zdrojak.cz. Mink nám poskytne veškerou funkcionalitu, stačí nám formulovat popis, vypracovat scénář a je hotovo.

Co má následující featura za lubem nám řekne sama. Vždyť testy jsou business-readable.

Feature: Uživatel si chce přečíst článek Michala Hatáka o mobilních technologiích

Uživatel se dozvěděl, že Michalovi Hatákovi vyšel zajímavý článek. Ve scénáři přijde na homepage a využije fulltextu. Asi nezná přesný název, proto hledá dle jména autora. Očekává, že ve výsledcích vyhledávání objeví článek o mobilních technologiích zmíněného autora.

Scenario: Uživatel chce najít fulltextovým vyhedáváním článek o mobilních technologiích od Michala Hatáka
    Given I am on homepage
    And the response status code should be 200
    When I fill in "Hledat" with "Michal Haták"
    And I press "Hledat"
    Then I should see "mobilní aplikace" in the "div #primary" element

Co se od Behatu dozvíme?

Výstup testů je přehledný. Jedná se o statistiku zobrazující chybové i správné testy, počet prošlých scénářů a čas. Potřebujeme-li získat více informací, můžeme Behat spustit ve verbose módu. Lze získat i screenshot chybové stránky. Klasický výstup si můžete prohlédnout na obrázku.

Behat: Výstup testu jedné feature.

Výstup testu jedné feature.

Nevyhovuje vám defaultní konfigurace? Nevadí!

Potřebujete-li přizpůsobit výchozí nastavení, soubor behat.yml je tím správným místem. Behat umožňuje nastavit více konfiguračních sad a lze mezi nimi přepínat. Jsou specifikovány názvem rootu. V našem případě použijeme jen jednu, a to výchozí default.

default:
  # Zpřístupníme knihovnu Mink, definujeme drivery
  extensions:
    Behat\MinkExtension\Extension:
      base_url: http://zdrojak.cz
      goutte: ~
      selenium2: ~

Ale to není vše, přátelé

Úmyslem mého článku není vytvářet návod. Funkcionalita Behatu, Minku, dalších rozšíření i myšlenky BDD jsou opravdu obsáhlé. Chtěla bych vám nastínit hlavně princip a cesty, kudy se můžete vydat. Předešlé testy si můžete stáhnout v tomto repozitáři. Buďte shovívaví. Jde jen o malé intro, napsané pro mé osobní kontroly ke kódům v článku. Třeba by se vám k prvnímu seznámení mohlo hodit.

Následující část prosluní i jiné zajímavé vychytávky, co v práci leckdy upotřebím. Budeme se věnovat běžné situaci, kdy uživatel chce odpovědět na inzerát. Ukázky jsou použity již z reálného projektu z mé práce.

Feature je opět velmi dobře srozumitelná.

Feature: Odpověď na inzerát
         Nepřihlášený uživatel chce odpovědět na inzerát.

Background

Je novinou této feature. Provede se před zahájením prvního scénáře a je společný všem scénářům v dané feature. V tomto případě vložíme do databáze testovací inzeráty.

Background:
    And following advertisements exist:
        | title          | workingPlace     | description |
        | Redaktor novin |  Humpolec        | ...         |
        | Php developer  |  Český krumlov   | ...         |

Tabulková data ve scénáři

V backgroundu vidíme způsob, jak lze manipulovat s obsáhlejšími daty. Behat je na straně scénáře přehledně zpracovává tabulkovým zápisem. Jednotlivé prvky jsou odděleny znakem “|”. Z každého řádku tabulky je vytvořeno pole. Názvy sloupců řádku prvního poslouží jako klíče prvků, jejich hodnoty získáme z obsahu buněk zbylých řádků tabulky. Jak s těmito daty naložit, si potom ukážeme ve FeatureContextu.

Formuláře levou zadní

Konkrétní scénář webtestu může vypadat následovně. První dva kroky jsou opět customizované, avšak nebudeme uvádět jejich implementaci. Od třetího řádku Gherkinu se nachází defaultní funkcionalita Minku. Ve scénáři uživatel vyplní i potvrdí odpovědní formulář a zkontroluje success message po odeslání. Práce s formuláři je díky Minku opravdu jednoduchá.

Scenario: Jako návštěvník chci odpovědět na inzerát se základním typem odpovědi.
    Given I am visitor
    When I am in front detail of that advertisement
    And I fill in "Celé jméno" with "Jan Novák"
    And I fill in "E-mail" with "jan.novak@example.com"
    And I fill in "Životopis" with "/tmp/cv.pdf"
    And I check "Souhlasím s podmínkami zpracování osobních údajů"
    And I press "Odpovědět"
    Then I should see "A je to. Tak ať vám to vyjde!"

FeatureContext pro webtesty

U webtestů je FeatureContext potomkem třídy MinkContext. Jinak tomu nebude ani v našem případě. Příklad si můžete prohlédnout.

use Behat\Gherkin\Node\TableNode;
use Entity\Advertisement;

class FeatureContext extends Behat\MinkExtension\Context\MinkContext
{

    /**
     * TIP: 
     * pomocí privátní proměnné můžeme k datům přitoupit napříč větami Gherkinu
     * @var \Entity\Advertisement
     */
    private $advertisement;

Ovlivňěte konkrétní část procesu

Anotace @BeforeScenario provede kód před všemi scénáři každé feature. K dispozici jsou i jiné užitečné hooky: @AfterScenario, @BeforeStep, @AfterStep. Pomocí nich a tagování lze ovlivnit i konkrétní kroky, scénáře, feature.

    /**
     * @BeforeScenario
     */
    public function init()
    {
        // for example connect database, load service container,
       // synchronize fulltext
    }

Pomocí třídy TableNode jednoduše zpracujeme tabulková data z backgroundu. V tomto případě vytvoříme sadu testovacích inzerátů.

    /**
     * @Given /^following advertisements exist:$/
     */
    public function followingAdvertisementsExist(TableNode $table)
    {
        $hashMap = $table->getHash();

        foreach ($hashMap as $row) {
            $advertisement = new Advertisement($row['title'], $row['desctiprtion']);
            $this->advertisement = $advertisement;
        }

       // poslední inzerát vložíme do privátní proměnné,
       // zpřístupníme ho tak ostatním metodách.
        return $this->advertisement;
    }

JavaScript ani AJAX nejsou překážkou

Pro jejich spuštění musíme zvolit jeden z driverů podporující Javascript: Sahi, Selenium, Selenium2, Zombie. Javascriptový test aktivujeme tagem @javascript před popisem scénáře. Tím se zpřístupní javascript_session.

@javascript
  Scenario: …

Nesmíme opomenout konfiguraci v souboru behat.yml a zpřístupnit session a browser.

    javascript_session: selenium2
    browser_name: firefox

Slovo zvenčí

Ráda bych vám přiblížila názory jiných, co s Behatem mají tu čest. Proto jsem neváhala a zeptala se Web QA Automation Specialisty Radima Daniela Pánka:

„Naši lidé potřebují testy psát rychle. Mít co nejmenší maintenance u již existujících testů, logické a jasné odpovědi, co a jak se přesně se testuje. A to díky jednomu jazyku Gherkin, kterému rozumí všechny zainteresované osoby. DSL sjednocuje lidi, neseparuje je na tábory, co testům rozumí a co ne. Prostřednictvím BDD není tester zahlcen na konci sprintu. I Behat má nějaké nedostatky, které snad budou ok ve třetí verzi (mluvím-li např o samotné exekuci napsaných testů), někdo má i menší problém využít již existující slovník, ale to je o jedinci.“

Také interakční designer a front-end developer Tomáš Hellebrand (@hellishcz) se podělil se svými zkušenostmi:

„Behat bylo moje první setkání s BDD. Hlavní přínos byl pro mne v tom, že striktně odděluje to co chci testovat od toho, jak to budu testovat. Testy navíc fungují jako živá dokumentace, která je srozumitelná a čitelná i pro ne-vývojáře. A to je velký benefit.“

Zvyšte si laťku, ale nos si nenatlučete

Behat je chytrý pomocník. Neztratí se v žádné části vývoje php aplikací. Developerům nesvazuje ruce ani nekrotí bujnou fantazii designerů. Odbourává komunikační propast směrem od vývojářů po byznys vrstvy, což často bývá kámenem úrazu. Je-li součástí developmentu vašich aplikací BDD, dozajista se vyřádíte. Není-li tomu tak, minimálně můžete získat řešení k zajištění aktuální specifikace projektů. Práce s Behatem není žádná věda, tak proč to nezkusit?

Programátorská víla z jižních Čech. V současnosti pracuji na pozici programáor/analytik ve Waldviertler Sparkasse BANK AG. Nejčastěji si hraji se Symfony, Nette, Doctrine, Codeception. Mám ráda přehledný a dobře čitelný kód. Mimo computer science jsem ráda v přírodě.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 10

Přehled komentářů

Olšo
Nikol Ježková Codeception
alesroubicek BDD
Nikol Ježková Re: BDD
alesroubicek Re: BDD
RDPanek Re: BDD
MilanLempera Jaké všechny nástroje pro testování používáte?
Nikol Ježková Re: Jaké všechny nástroje pro testování používáte?
lubosdz Velmi pekne priklady
Nikol Ježková Re: Velmi pekne priklady
Zdroj: https://www.zdrojak.cz/?p=12926