J’ai récemment découvert Behat et comme je trouve le concept très intéressant, j’ai pensé que quelques informations intéresseraient peut-être nos lecteurs pendant mon initiation à cette nouvelle forme de tests unitaires.
Behat a été créé dans le cadre d’une méthode de développement agile “Behavior Driven Development”, je vous recommande la lecture de l’article Wikipédia qui traite du BDD pour plus d’information car comme je ne connais bien le concept, je vais écrire des choses triviales à ce sujet.
L’idée de Behat, qui s’inspire de projets pour d’autres langages (comme Cucumber pour Ruby), est d’écrire des tests en langage naturel. Voici l’exemple de la documentation officielle de Behat (pardonnez le manque de coloration syntaxique, mais le langage n’est pas supporté dans le plugin Wordpress) :
Feature: ls
In order to see the directory structure
As a UNIX user
I need to be able to list the current directory's contents
Scenario:
Given I am in a directory "test"
And I have a file named "foo"
And I have a file named "bar"
When I run "ls"
Then I should get:
"""
bar
foo
"""
C’est en voyant cet exemple sur Twitter que j’ai découvert Behat, intrigué par cette façon de faire, assez éloignée de la façon de tester avec des outils de type xUnit.
Je vous propose de créer une petite classe PHP et de la tester rapidement pour découvrir Behat.
Tout d’abord, installons Behat. Pour faire simple on va créé notre projet et y installer Behat :
mkdir behatest && cd behatest
wget https://github.com/downloads/Behat/Behat/behat.phar
php behat.phar --init
La dernière commande va créer l’arborescence de fichier pour que nous puissions commencer à travailler. Tout se passe dans le répertoire features. Je ne vous invite pas à découvrir l’arborescence car nous nous y baladerons petit à petit.
Pour nous donner du grain à moudre (j’ai toujours rêvé d’écrire ça un jour, allez savoir pourquoi…), nous allons écrire rapidement une petite classe PHP qui va représenter une voiture (soyons original).
<?php
class Voiture {
/**
* Détermine la vitesse maximum du véhicule
*/
const VITESSE_MAX = 130;
/**
* La vitesse de la voiture
*/
protected $vitesse;
/**
* La distance parcourue par la voiture
*/
protected $compteur;
public function __construct($defaultVitesse)
{
$this->vitesse = $defaultVitesse;
$this->compteur = 0;
}
/**
* Augmente la vitesse de $delta km/h
* @param int $delta en km/h
*/
public function accelere($delta)
{
if($this->vitesse + $delta > self::VITESSE_MAX)
{
$this->vitesse = self::VITESSE_MAX;
}
else
{
$this->vitesse += $delta;
}
}
/**
* Fait avancer la voiture pendant un temps donné en heure
*/
public function avancePendant($temps)
{
$this->compteur += $this->vitesse * $temps + 1;
}
/**
* Retourne la vitesse actuelle
* @return int en km/h
*/
public function getVitesse()
{
return $this->vitesse;
}
/**
* Retourne la valeur du compteur
* @return int en km
*/
public function getCompteur()
{
return $this->compteur;
}
}
Nous devons à présent tester les deux fonctionnalités présentes à savoir " accelere" et " avancePendant". Pour cela nous allons écrire des scénarios. Commencez par créer le fichier voiture.feature dans le dossier features.
Il nous faut décrire l’objet du test. Cette partie est purement descriptive !
Feature: voiture
Pour faire fonctionner une voiture
Jai besoin de pouvoir accélerer et avancer pendant un certain temps.
Intéressons-nous à l’écriture des tests eux-mêmes. En fait, avec Behat, on n’écrit pas des tests mais des scénarios structurés en étapes :
Scenario: Some description of the scenario
Given [some context]
When [some event]
Then [outcome]
Attaquons tout de suite avec l’écriture du scénario de la méthode " avancePendant", à la suite de la description de la Feature.
Scenario: Fait avancer une voiture pendant 1 heure
Given I have a car with a default speed of "50" km/h
When I drive for "1" hour
Then the counter should be at "50" km
Remarquez que j’ai écris la description en français et le scénario en anglais. Behat supporte plusieurs langages (via la bibliothèque Gherkin) pour ses mots-clés (Nous avons utilisés les mots-clés suivant jusqu’à présent : Features, Scenario, Given, When et Then), dont le français, l’allemand et l’incontournable “English Pirate”. Il ne regarde donc que la langue des mots-clés pour comprendre ce que vous voulez lui faire faire, le reste peut être un mélange de franglais, Behat s’en moque, nous allons voir dans quelques instants pourquoi.
Nous avons écrit notre premier scénario, il nous faut maintenant l’exécuter. Prenez soin de l’exécuter dans le dossier behatest/ car comme nous avons écrit le code un peu à la méthode manouche, il n’y a pas d’autoload etc. (important pour la suite).
php behat.phar
Vous devriez obtenir quelque chose du genre :
Nous avons donc un scénario composé de 3 étapes. Mais WTF ? Le test n’a ni réussi ni échoué ? Oui, parce qu’il nous faut encore définir le code à exécuter pendant les étapes ! Vous n’aviez quand même pas cru que Behat ferait tout tout seul, hein ? :P
Utilisons les propositions de Behat (les trois méthodes à la fin de l’exécution) pour définir nos étapes. Ouvrez le fichier FeatureContext.php qui doit se situer dans behat/features/bootstrap/. C’est dans ce fichier que tout se passe réellement. Commencez par inclure la classe Voiture.
require_once 'Voiture.php'
On ajoute donc les méthodes copiées depuis le terminal dans la classe FeatureContext. Chaque méthode est une étape. La magie s’opère ici : l’annotation (une expression régulière) au-dessus de la méthode permet de lier la méthode à ce que vous avez écrit précédemment dans votre scénario. C’est aussi pour ça que la langue n’importe peu !
Voici l’implémentation des méthodes (je vous recommande d’implémenter les méthodes une à une, et d’exécuter le test à chaque fois pour voir les différentes phases) :
/**
* @Given /^I have a car with a default speed of "([^"]*)" km/h$/
*/
public function iHaveACarWithADefaultSpeedOfKmH($defaultSpeed)
{
$this->fixtures = new Voiture($defaultSpeed);
}
/**
* @When /^I drive for "([^"]*)" hour$/
*/
public function iDriveForHour($hour)
{
$this->fixtures->avancePendant($hour);
}
/**
* @Then /^the counter should be at "([^"]*)" km$/
*/
public function theCounterShouldBeAtKm($km)
{
if($this->fixtures->getCompteur() != $km)
{
throw new Exception("Le compteur est à ".$this->fixtures->getCompteur());
}
}
Exécutons finalement notre test :
Il semblerait qu’une erreur soit présente dans la classe, il suffit d’aller consulter la méthode incriminée et de corriger l’erreur :
/**
* Fait avancer la voiture pendant un temps donné en heure
*/
public function avancePendant($temps)
{
$this->compteur += $this->vitesse * $temps;
}
Exécutez une nouvelle fois le test :
Tout il est beau, tout il est vert, le bonheur du développeur ;-)
Maintenant que vous avez, je l’espère, saisi le principe, je vous invite à écrire un nouveau scénario pour la méthode " accelere". Si vous avez la flemme mais que vous voulez quand même voir, vous pouvez retrouver le code source du petit tutoriel de découverte (avec un scénario supplémentaire) sur mon github.
Ceci n’était qu’un tout petit aperçu rikiki et surtout sans prétentions des possibilités de Behat. Je vous encourage, si l’outil vous intéresse, à consulter la documentation officielle et tout particulièrement à voir comment il peut être couplé à Mink pour réaliser des tests fonctionnels.
Au niveau technique, je suis quand même moins à l’aise avec Behat que les outils que j’utilise d’habitude (xUnit) et je ne suis pas un grand fan de ces textes descriptifs qui correspondent à des fonctions à la longue.
Cependant, je trouve intéressant le mimétisme avec les “histoires utilisateur” de la méthode SCRUM. Ce test aurait très bien pu être écrit par un client (ou pas loin) et dans tous les cas, il est capable de comprendre que si ce scénario est correctement exécuté, tout va bien ;-)
L’outil a l’intérêt d’être une interaction supplémentaire avec les autres acteurs qui ont parfois bien du mal à nous comprendre les développeurs. J’attends avec impatience ses évolutions et de voir comment il pourrait être utilisé en complément des tests plus traditionnels à titre “pédagogique”.