Behat : BDD avec PHP, Symfony, Selenium et Jenkins

Logo BehatBehat est un framework de test Behavior-Driven Development pour PHP.

Les tests écrits avec Gherkin permettent une meilleure communication entre les développeurs et le business, et une meilleure fiabilité de l’application.

Behat est développé en utilisant une grande partie des composants Symfony 2.

If you are a stakeholder, this is your proof that developers understand exactly how you want this feature to work. If you are a developer, this is your proof that the stakeholder expects you to implement this feature exactly in the way you’re planning to implement it.

Les scénarios des tests s’écrivent dans ce qui s’appelle des features.
Une feature = un fichier xxxxx.feature
Un scénario contient habituellement les étapes suivantes : Given / When / Then / And (alias de Then)

Une traduction des mots clefs est disponible en français mais il préférable de s’en tenir à l’anglais (des soucis peuvent survenir avec certains caractères), si besoin :

sudo apt install cucumber
cucumber --i18n fr

Exemple de feature

Feature: Product basket
  In order to buy products
  As a customer
  I need to be able to put interesting products into a basket
 
  Scenario: Buying a single product under 10€
    Given there is a "Sith Lord Lightsaber", which costs 5€
    When I add the "Sith Lord Lightsaber" to the basket
    Then I should have 1 product in the basket
    And the overall basket price should be 9€

Il est possible d’utiliser des table-like ou paragraphe en utilisant les PyString """.
Exemple avec un tableau :

Feature: Get users
    In order to check users
    As an admin
    I must have a list of all of them
 
    Scenario: List users
        Given there are users:
        | username      | password | email           |
        | foo           | 123456   | foo@bar.com     |
        | bar           | 22@222   | bar@foo.com     |
        When I search the user "foo"
        Then I should find it and get the email "foo@bar.com"

Tags

Il est possible de renseigner un tag au niveau de la feature, cela permet de préciser des contextes seulement pour certains tags, de lancer les tests sur une liste de tags etc.

@backoffice
Feature: Get users
    In order to check users
    ...

Installation

composer require --dev behat/behat
vendor/bin/behat --init

La deuxième commande créer les fichiers d’init de Behat.

Coloration

Behat est nativement supporté par PHPStorm, pour Sublime Text il existe un package afin d’avoir la coloration syntaxique.
Behat Sublime Text
Les valeurs en jaunes sont récupérées dans le code afin d’être testées.

Lancer les tests

Une fois les features créées dans le dossier features/, on peut lancer les tests :

vendor/bin/behat # toutes les features
vendor/bin/behat features/basket.feature # une feature en particulier
vendor/bin/behat --tags backoffice # toutes les features qui ont le tag backoffice

Pour le moment pas grand chose à tester, afin de créer automatiquement les fonctions et leurs arguments, voici la commande magique proposée :

vendor/bin/behat --dry-run --append-snippets

Behat créer les fonctions comme ceci :

/**
 * @Given there is a :arg1, which costs :arg2€
 */
public function thereIsAWhichCostsEur($arg1, $arg2)
{
    
}

/**
 * @When I add the :arg1 to the basket
 */
public function iAddTheToTheBasket($arg1)
{
    
}

/**
 * @Then I should have :arg1 product in the basket
 */
public function iShouldHaveProductInTheBasket($arg1)
{
    
}

/**
 * @Then the overall basket price should be :arg1€
 */
public function theOverallBasketPriceShouldBeEur($arg1)
{
    
}

Il ne reste plus qu’à remplir le contenu des fonctions avec du code métier, et d’y ajouter quelques assertions PHPUnit.

Exemple simple :

use Behat\Behat\Context\Context;
use PHPUnit\Framework\Assert;

class BasketContext implements Context
{
    const COST_SHIPPING = 4;

    private $products = [];
    private $basket = [];

    public function __construct()
    {
    }

    /**
     * @Given there is a :arg1, which costs :arg2€
     */
    public function thereIsAWhichCostsEur($arg1, $arg2)
    {
        $product = new \stdClass();
        $product->name = $arg1;
        $product->price = $arg2;
        $this->products[$product->name] = $product;
    }

    /**
     * @When I add the :arg1 to the basket
     */
    public function iAddTheToTheBasket($arg1)
    {
        if (\array_key_exists($arg1, $this->products)) {
            $this->basket[] = $this->products[$arg1];
        }
    }

    /**
     * @Then I should have :arg1 product in the basket
     */
    public function iShouldHaveProductInTheBasket($arg1)
    {
        Assert::assertGreaterThan(0, \count($this->basket));
    }

    /**
     * @Then the overall basket price should be :arg1€
     */
    public function theOverallBasketPriceShouldBeEur($arg1)
    {
        $amount = self::COST_SHIPPING;
        foreach ($this->basket as $product) {
            $amount += $product->price;
        }

        Assert::assertEquals($arg1, $amount);
    }
}

Fichier Bootstrap

Le Bootstrap de Behat peut être surchargé.

Par défaut il est créé ici : features/bootstrap/FeatureContext.php

Il est possible d’ajouter des instructions à des moments précis du déroulement des tests : BeforeScenario, BeforeFeature, Then, When etc.

Exemple :

/** @BeforeFeature */
public static function prepareForTheFeature()
{
    // clean database or do other preparation stuff
}

/**
 * @BeforeScenario @backoffice
 */
public function createSchema()
{
    echo 'je passe ici';
}

/** @Given we have some context */
public function prepareContext()
{
    // do something
}

/** @When event occurs */
public function doSomeAction()
{
    // do something
}

/** @Then something should be done */
public function checkOutcomes()
{
    // do something
}

A noter que la fonction createSchema ne s’exécutera que pour les features contenant le tag « backoffice »

Affichage des résultats

Pour sortir le résumé sur la sortie standard (console) et le résultat XML Junit dans un fichier /behat-output/default.xml

vendor/bin/behat -f progress -o std -f junit -o behat-output --strict

Tests navigateurs

Afin de réaliser des tests Selenium depuis Behat, il faut installer plusieurs outils, voici un combo possible : JAR Selenium standalone + chromedriver + Mink
Behat Selenium Mink Chrome

Mink est un outil open source, browser emulator écrit en PHP, l’installation se faire via composer (Cf. next step).

Il faut ensuite récupérer le JAR Selenium sur le site officiel, puis le driver chrome (lien à modifier en fonction de la version en cours).

Quelques lignes de commande afin d’utiliser chromedriver :

unzip ~/Downloads/chromedriver_linux64.zip -d ~/Downloads
chmod +x ~/Downloads/chromedriver
sudo mv -f ~/Downloads/chromedriver /usr/local/share/chromedriver
sudo ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver
sudo ln -s /usr/local/share/chromedriver /usr/bin/chromedriver

Si tout s’est bien passé il est maintenant possible de lancer Selenium de cette façon :

java -Dwebdriver.chromedriver=/usr/bin/chromedriver -jar ~/Downloads/selenium-server-standalone-3.12.0.jar

Voici un exemple de feature qui va réaliser les tests navigateurs, et qui fonctionne avec les directives natives Behat/Mink et donc ne nécessite pas de classe PHP !

@selenium
Feature: User profile
    I can see my name on my profile

    Scenario: Dashboard page shows my name
        Given I am on "/sign-in"
        Then I should see "Sign in"
        When I fill in "email" with "foo@bar.com"
        And I fill in "password" with "123456"
        And I press "submit"
        Then I should see "Name: Foo"

Intégration à Symfony

Installer les packages nécessaires :

composer require --dev behat/symfony2-extension
composer require --dev behat/mink
composer require --dev behat/mink-extension
composer require --dev behat/mink-browserkit-driver
composer require --dev behat/mink-selenium2-driver

Modifier le fichier de conf behat.yml généré à la racine du projet :

default:
    suites:
        default:
            type: symfony_bundle
            bundle: AppBundle
            contexts:
                - FeatureContext:
                    mySymfonyParam: "%%db_host%%"
                    doctrine: "@doctrine"
                - AppBundle\Features\Context\BasketContext:
                    basket: "@app.basket"
                - AppBundle\Features\Context\UsersContext:
    extensions:
        Behat\Symfony2Extension:
            kernel:
                env: "dev"
                debug: "true"
        Behat\MinkExtension:
            base_url: http://myapp.my.company/web/
            browser_name: 'chrome'
            sessions:
                default:
                    selenium2: ~

Dans l’exemple ci-dessus on voit qu’il est possible de passer en argument des paramètres et des services Symfony aux classes Behat.

Intégration à Jenkins

Il est nécessaire d’abord de configurer le lancement de Selenium pour l’utilisateur Linux qui va lancer les jobs (utilisateur jenkins en l’occurrence).
Cela peut se faire via une CRON qui exécutera le script suivant :

#!/bin/bash

Xvfb -ac :99 -screen 0 1280x1024x16 &
export DISPLAY=:99
java -Dwebdriver.chromedriver=/usr/bin/chromedriver -jar ~/behat/selenium-server-standalone-3.12.0.jar

Et pour le crontab :

@reboot ~/behat/script-demarrage.sh

Pour vérifier que Selenium se lance bien après reboot, exécuter la commande suivante (le port 4444 doit être visible) :

netstat -tln

Ensuite il suffit d’ajouter l’instruction Behat au Pipeline Jenkins :

stage('Tests Behat') {
    def path = pwd()
    sh "${path}/vendor/bin/behat -f progress -o std --strict"
}

Si les tests échouent, le job retournera un fail.

Share Button

Laisser un commentaire.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.