Le Behavior Driven Develoment (ou BDD pour les intimes) est une méthode qui permet une meilleur collaboration entre les développeurs et les responsables projets. Le BDD utilise un langage naturel (comprehensible par tout le monde, pas seulement par les dev) qui permet à tous les intervenants de communiquer simplement sur les fonctionnalitées de l'application.

Behat est un framework PHP pour faire du BDD en PHP. Inspiré par Cucumber, Behat utilise la syntax Gherkin qui permet de décrire les scénario. Par exemple imaginons que nous déveleppons un nouveau moteur de recherche :

Feature: Mon Super moteur de Recherche

  Scenario: Homepage
    Given I am on the homepage
    Then I should see "Recherche"

  Scenario: Search
    Given I am on the homepage
    When I fill in "q" with "Grafikart"
    And I wait for 1 seconds
    Then I should see "Grafikart.fr"

Ensuite, il nous suffit d'implémenter ces scénarios pour considérer notre travaille comme finit. Le problème c'est que notre site est développé en PHP, alors comment ce langage va pouvoir comprendre et tester notre code ? C'est là que Behat intervient.

Installation

L'installation n'a rien de bien complexe gràce à notre ami composer, il nous suffit donc de l'ajouter à notre composer.json

"require": {
        "behat/behat": "3.0.x-dev"
 }

Puis un simple composer update et le tour est joué. Cela va ajouter un executable à l'intérieur du dossier bin de composer (par défaut /vendor/bin) qui va nous permettre d'initialiser behat

./vendor/bin/behat --init

Cette commande va permettre de créer un dossier features qui contiendra nos features mais aussi nos context (ils permettent la traduction de nos phrases en code testable).

Notre premier scénario

Pour s'essayer au BDD et à Behat nous allons créer une fonctionnalité simple. Pour cela on doit créer un fichier .feature dans le dossier /features.

Une fonctionnalité est toujours décrite de la même façon avec des mots clef très importants.

  • Feature:, permet de donner un nom à notre fonctionnalité, on peut ajouter à la suite du texte indenter pour décrire plus en profondeur cette dernière
  • Scenario:, permet de commencer un nouveau scénario, un nouveau cas d'utilisation
    • Given, permet de donner un contexte
    • When, définit une action
    • Then, définit un résultat
    • And

Donc si on prend en exemple un panier d'achat que l'on va remplir d'un produit qui coute 10 €

Feature: Basket

  Scenario: Basket with one product
    Given an empty basket
    When I add a new product costing 10 € to the basket
    Then the basket price is 10 €

Il est possible d'écrire les tests en français ou dans d'autres langues, afin de se faire comprendre par le plus grand nombre (surtout dans le cas de projets open source) nous allons écrire les scénarios en anglais.

Tester son scénario

Maintenant que notre scénario est écrit on peut le tester gràce à la commande behat

./vendor/bin/behat

Malheureusement behat ne sait pas deviner comment tester notre scénario, on va devoir "traduire" nos phrases en code PHP pour lui dire comment tester notre application. La commande vous donne une structure de base que vous allez pouvoir utiliser. Pour le moment nous allons implémenter les méthodes dans FeatureContext mais on peut se créer des contextes différents suivant les cas.

class FeatureContext implements Context, SnippetAcceptingContext
{
    /**
     * @Given an empty basket
     */
    public function anEmptyBasket()
    {
        $this->basket = new Basket();
    }   

    /**
     * @When  I add a new product costing :arg1 € to the basket
     */
    public function aProductCostingIsAddedToTheBasket($arg1)
    {
        $product = Product::withCost($arg1);
        $this->basket->addProduct($product);
    }

     /**
     * @Then The basket price is :arg1 $
     */
    public function theBasketPriceIs($arg1)
    {
        if($this->basket->price() != $arg1){
            throw new Exception('Le prix ne correspond pas');
        }
    }
}

Un test qui échoue devra renvoyer une exception expliquant le problème (on pourra plus tard utiliser des librairies tiers pour lever des exception automatiquement). Si on relance la commande ./vendor/bin/behat nous allons obtenir des erreurs car nos class n'existent pas encore.

Il nous suffit donc maintenant d'écrire le code permettant de faire fonctionner le scénario que l'on a écrit, pour que tout passe au vert (je vous laisse le plaisir de le faire). Pour tester si notre scénario fonctionne il nous suffit de relancer la commande behat.

Tester l'interface

Un autre avantage du BDD c'est la possibilité de lancer des tests sur l'application directement (en simulant une navigation via navigateur). Pour cela on va devoir installer mink, mink-extension et quelques drivers.

"behat/mink": "1.6.*",
"behat/mink-extension": "*",
"behat/mink-goutte-driver": "*",
"behat/mink-selenium2-driver": "*"

Ici nous installerons 2 drivers :

  • goutte permet de naviguer et de parcourir un site en PHP (sans utiliser de réel navigateur)
  • selenium2 (http://www.seleniumhq.org/) est une librairie java qui permet de prendre le contrôle d'un navigateur (firefox par exemple) pour simuler une navigation réelle.

Mink extension permet de faire la liaison entre behat et mink et d'utiliser un context qui contient pleins de fonctions sympas pour intéragir avec les différents drivers.
Nous allons essayer tout ça à travers un scénario de recherche google

@ui
Feature: Google

  Scenario: Homepage
    Given I am on the homepage
    Then I should see "Recherche Google"

  @javascript
  Scenario: Search
    Given I am on the homepage
    When I fill in "gbqfq" with "Grafikart"
    And I wait for 1 seconds
    Then I should see "Grafikart.fr"

J'ai ici un tag @ui permettant de dissocier les scénarios qui utiliseront ce nouveau contexte. Le tag @javascript est automatiquement compris par behat et permet de désigner les scénarios qui auront besoin de javascript, et du coup de lancer un navigateur réel (via selenium). Selenium étant relativement long (car il doit charger chaque page) il est conseillé de ne pas le mettre partout sous peine de devoir attendre longtemps pour valider nos scénarios.

Il va nous falloir maintenant guider behat pour lui demander comment effectuer ces tests. Pour cela on peut créer un fichier behat.yml à la racine de notre projet.

default:
  extensions:
    Behat\MinkExtension:
      base_url: http://google.fr/
      goutte: ~
      selenium2:
        wd_host: http://localhost:4444/wd/hub
  suites:
    core:
      contexts: [FeatureContext, BasketContext]
      filters: { tags: '@core' }
    ui:
      contexts: [FeatureContext, WebContext]
      filters: { tags: '@ui' }

L'idée est ici de créer 2 tags qui auront chacun des contextes différents.

  • @ui permet de désigner un comportement lié à l'application / interface
  • @core permet de décrire un comportement dans le core (code php) de notre application

Maintenant nous pouvont créer notre context WebContext qui va devoir hériter de MinkExtension

<?php

use Behat\MinkExtension\Context\MinkContext;

class WebContext extends MinkContext{

    /**
     * @When I wait for :arg1 seconds
     */
    public function iWaitForSeconds($arg1)
    {
        $this->getSession()->wait($arg1 * 1000);
    }

}

Enfin nous pouvons maintenant lancer nos tests et voir toutes nos lignes passer au vert :)