BDD: from novice to ninja
Finalmente ho trovato il tempo per definire nel modo che volevo i test del software web che costruisco.
Questo post lo scrivo come promemoria per me e per chi vuole addentrarsi in questo mondo.
Le idee che stanno alla base di questa metodologia sono:
- scrivere dei test da far girare prima di ogni deploy del software web
- voglio scrivere poco codice php per i test
- i test devono essere comprensibili al cliente (o meglio ai business analysts)
- i test devono testare l’effettivo risultato sul browser
- i test devono darmi delle chiare indicazioni di dove falliscono
- devono funzionare nel mio ambiente di sviluppo cioè symfony 2.1 + vagrant (per i dettagli vedi Impostare il mio ambiente di sviluppo perfetto in OSX 10.8 Mountain Lion)
L’idea: behavior-driven development (BDD)
Nell’ambito dell’ingegneria del software, il behavior-driven development (abbreviato in BDD e traducibile in Sviluppo guidato dal comportamento) è una metodologia di sviluppo del software basata sul test-driven development (TDD)[1][2] Il BDD combina le tecniche generali e i principi del TDD con idee prese dal domain-driven design e dal desing e all’analisi orientato agli oggetti per fornire agli sviluppatore software e ai Business analysts degli strumenti e un processo condivisi per collaborare nello sviluppo software.[1][3]
Per quanto BDD sia principalmente un’idea di come lo sviluppo del software dovrebbe essere gestito sia da interessi di business e analisi tecniche, la pratica della BDD assume l’utilizzo di strumenti software specializzati per supportare il processo di sviluppo.[2] Sebbene questi strumenti siano spesso sviluppati in particolare per essere utilizzati in progetti BDD, possono essere visti anche come delle forme specializzate degli strumenti che supportano la TDD. Gli strumenti servono per aggiungere automazione all’ubiquitous language che è il tema centrale della BDD.
Se votete approfondire c’è un semplice articolo di Dan North dal titolo Introducing BDD di cui trovate la traduzione in italiano qui.
Ecco un test scritto con BDD:
Feature: Login In order to login As a portal user I need to be able to validate the username and password against portal Scenario: Link to login page Given I am on the homepage When I go to "Accedi" Then I am on "login" @javascript Scenario: Login as an existing user Given I am on "login" When I fill in "username" with "userU" And I fill in "password" with "password" And I press "Entra nel portale" Then I should see "Benvenuto firstU (utente)" @javascript Scenario: Login as an existing powered user Given I am on "login" When I fill in "username" with "userPowered" And I fill in "password" with "password" And I press "Entra nel portale" Then I should see "Benvenuto UtenteP (user potenziato)" @javascript Scenario: Login as an existing user admin Given I am on "login" When I fill in "username" with "admin" And I fill in "password" with "password" And I press "Entra nel portale" Then I should see "Benvenuto UtenteA (amministratore)" @javascript Scenario: Login as an unexisting user Given I am on "login" When I fill in "username" with "adminasdasd" And I fill in "password" with "moodlesdfasdf" And I press "Entra nel portale" Then I should see "Bad credentials"
Il linguaggio utilizzato è il Gherkin e come vedete ricalca molto da vicino quelle che sono le user stories.
Su come e cosa scrivere in questo test vi segnalo due risorse:
- http://www.slideshare.net/oehsani/bdd-in-ddd (in particolare dalla slide 9 e seguenti)
- Writing Features – Gherkin Language
I collanti: Behat, Mink e Selenium2
Le tecnologie che verranno usate per raggiungere l’obiettivo sono:
- Behat: è un framework behavior-driven development open source per PHP 5.3 e 5.4. Vedi: http://docs.behat.org/
- Mink: per verificare che l’applicazione web si comporti correttamente abbiamo bisogno di un modo per simulare l’interazione tra applicazione e browser, Mink è un framework open source per acceptance test scritto in PHP 5.3. Vedi: http://mink.behat.org/
- Selenium2: simile a Mink ma con la possibilità di “comandare” i browser effettivamente presenti nel nostro ambiente. Useremo la versione selenium2 server che ci permetterà di testare anche pagine con javascript. Vedi http://docs.seleniumhq.org/
Un esempio pratico: fos_user_bundle
Come esempio e promemoria testiamo il bundle fos_user_bundle che permette l’autenticazione degli utenti (e non solo) in un progetto symfony2.1.
Installazione dei collanti.
Per installare i Behat, Mink e tutti gli strumenti necessari basta modificare il file composer.json aggiungendo le seguenti righe:
"config": { "bin-dir": "bin/" }, "require": { [...........] "behat/behat": "2.4.*@stable", "behat/mink": "1.4.*@stable", "behat/symfony2-extension": "*", "behat/mink-extension": "*", "behat/mink-browserkit-driver": "*", "behat/mink-selenium2-driver": "*", "behat/mink-goutte-driver": "*", "behat/mink-sahi-driver": "*", "phpunit/phpunit": "3.7.*" },
Poi digitare il solito:
composer update
che si preoccuperà di ottenere i vari pacchetti e le relative dipendenze. Verrà creata anche la cartella bin con l’eseguibile behat.
Salviamo in una cartella anche l’ultima version disponibile del file .jar di selenium 2 server (ad esempio http://selenium.googlecode.com/files/selenium-server-standalone-2.33.0.jar)
Ora abbiamo tutti i nostri strumenti.
Configurazione e inizializzazione
Configuriamo Behat creando il file app/config/behat.yml con il seguente contenuto:
default: formatter: name: progress extensions: Behat\Symfony2Extension\Extension: mink_driver: true kernel: env: test debug: false Behat\MinkExtension\Extension: base_url: 'http://localhost:8080/' javascript_session: 'selenium2' browser_name: firefox goutte: ~ selenium2:
Si noti l’integrazione tra le diverse componenti e l’utilizzo del server web sulla porta 8080 che vagrant ha messo a disposizione.
A questo punto possiamo procedere all’inizializzazione dell’ambiente di test con il comando:
./bin/behat --init --config=app/config/behat.yml @ZenPortalBundle
Questo comando crea la struttura delle cartelle nel vostro progetto, il tutto contenuto nel folder Features. Questa sarà la cartella di riferimento per i nostri test.
Creazione di un test
Per creare un test BDD basta scrivere un file .feature nel linguaggio Gherkin e posizionarlo nella cartella Features.
Ecco quello che useremo (file login.feature):
Feature: Login In order to login As a portal user I need to be able to validate the username and password against portal Scenario: Link to login page Given I am on the homepage When I go to "Accedi" Then I am on "login" @javascript Scenario: Login as an existing user Given I am on "login" When I fill in "username" with "userU" And I fill in "password" with "password" And I press "Entra nel portale" Then I should see "Benvenuto firstU (utente)" @javascript Scenario: Login as an existing powered user Given I am on "login" When I fill in "username" with "userPowered" And I fill in "password" with "password" And I press "Entra nel portale" Then I should see "Benvenuto UtenteP (user potenziato)" @javascript Scenario: Login as an existing user admin Given I am on "login" When I fill in "username" with "admin" And I fill in "password" with "password" And I press "Entra nel portale" Then I should see "Benvenuto UtenteA (amministratore)" @javascript Scenario: Login as an unexisting user Given I am on "login" When I fill in "username" with "adminasdasd" And I fill in "password" with "moodlesdfasdf" And I press "Entra nel portale" Then I should see "Bad credentials"
Integrazione con Mink e selenium2
Ora si tratta di sistemare il file FeatureContext.php per integrarlo con Mink. Visto che mettiamo mano a questo file ne approfittiamo anche per alcune aggiunte utili. Aggiungiamo una porzione di codice che nel caso in cui un test fallisce mi crea nella cartella build una immagine con lo screenshot del browser nel punto in cui il test fallisce e con un nome significativo (lo scenario).
<?php namespace Zen\PortalBundle\Features\Context; use Symfony\Component\HttpKernel\KernelInterface; use Behat\Symfony2Extension\Context\KernelAwareInterface; use Behat\MinkExtension\Context\MinkContext; use Behat\Mink\Exception\ElementNotFoundException; use Behat\Mink\Exception\UnsupportedDriverActionException; use Behat\Mink\Driver\Selenium2Driver; // // Require 3rd-party libraries here: // //require_once 'PHPUnit/Autoload.php'; //require_once 'PHPUnit/Framework/Assert/Functions.php'; // /** * Feature context. */ class FeatureContext extends MinkContext implements KernelAwareInterface { private $kernel; private $parameters; /** * Initializes context with parameters from behat.yml. * * @param array $parameters */ public function __construct(array $parameters) { $this->parameters = $parameters; } /** * Sets HttpKernel instance. * This method will be automatically called by Symfony2Extension ContextInitializer. * * @param KernelInterface $kernel */ public function setKernel(KernelInterface $kernel) { $this->kernel = $kernel; } /** * Take screenshot when step fails. * Works only with Selenium2Driver. * * @AfterStep */ public function takeScreenshotAfterFailedStep($event) { if (4 === $event->getResult()) { $driver = $this->getSession()->getDriver(); if (!($driver instanceof Selenium2Driver)) { // throw new UnsupportedDriverActionException('Taking screenshots is not supported by %s, use Selenium2Driver instead.', $driver); return; } $directory = 'build/behat/'.$event->getLogicalParent()->getFeature()->getTitle().'.'.$event->getLogicalParent()->getTitle(); if (!is_dir($directory)) { mkdir($directory, 0777, true); } $filename = sprintf('%s_%s_%s.%s', $this->getMinkParameter('browser_name'), date('c'), uniqid('', true), 'png'); file_put_contents($directory.'/'.$filename, $driver->getScreenshot()); } } public function assertPageContainsText($text) { $this->getSession()->wait(10000, '(typeof(jQuery)=="undefined" || (0 === jQuery.active && 0 === jQuery(\':animated\').length))'); parent::assertPageContainsText($text); } }
Esecuzione del test
Ora siamo pronti ad eseguire il test.
Prima di tutto facciamo partire il server selenium con il comando:
java -jar selenium-server-standalone-2.33.0.jar
Poi facciamo partire i nostri test con:
./bin/behat --config=app/config/behat.yml @ZenPortalBundle
Se tutto è andato a buon fine vedremo avanzare i test attraverso i browser e il server selenium e otteniamo un bel verde alla fine.
Fonti
Alcune risorse che ho trovato utili in questo studio:
- http://cdn.oreillystatic.com/en/assets/1/event/61/Top%20Shelf%20PHP%20Presentation.pdf
- http://shashikantjagtap.net/cross-browser-testing-with-behat-mink-and-selenium/
- http://zubte.com/blog/behat-mink-sahi-and-selenium/
- http://michaelheap.com/behat-selenium2-webdriver-with-minkextension/
- https://github.com/nulpunkt/behat-mink-selenium/blob/master/behat.yml
- http://blog.testingbot.com/2012/02/10/selenium-testing-with-mink-and-behat
In modo particolare segnalo l’intervento di Tuin al phpDay 2013 (http://2013.phpday.it/talk/automated-acceptance-testing-with-behat-and-mink/) di cui qui trovare le slide.
ARTICOLI
sviluppo web