BEHAT : STRUCTUREZ VOS TESTS FONCTIONNELS

Il est important de mettre en place des tests fonctionnels sur les projets afin de s’assurer du bon fonctionnement de l’application. Lorsqu’il s’agit d’une application Symfony, Behat est l’outil le plus souvent utilisé pour réaliser ces tests et c’est tant mieux car cet outil est très complet. Il faut néanmoins savoir l’utiliser à bon escient afin de couvrir des cas de tests utiles et complets, c’est ce que nous allons voir dans cet article.

Introduction

Lorsque nous parlons de “tests fonctionnels”, nous entendons bien souvent vouloir tester l’interface de l’application (site web), autrement dit, automatiser des tests qui pourraient être faits par un humain. Or, il est important d’écrire les cas de tests suivants afin de couvrir le périmètre fonctionnel : tests d'interface : il s’agit de réaliser des contrôles d’interface pour s’assurer que le comportement de l’application web réagit correctement, Les tests d'intégration : il s’agit de s’assurer que le code (testé unitairement) qui fait tourner l’application réagit bien comme il le devrait lorsque tous les éléments sont assemblés. Il conviendra alors de lancer à la fois les tests d’intégration et les tests d’interface avec Behat. Avant de commencer, notez que dans cet exemple, nous allons utiliser un serveur Selenium qui recevra les informations fournies par Mink (extension de Behat) et qui pilotera ensuite notre navigateur (Chrome, dans notre configuration). Pour être clair sur l’architecture, voici un schéma qui résume le rôle de chacun :



Fonctionnement de Behat

Avant de passer à la partie installation et configuration il est préférable de comprendre le fonctionnement de Behat. Ce guide sera découpé en 9 partie :

      1. Gherkin Syntax
      2. Features
      3. Scenarios
      4. Scenario Outlines
      5. Backgrounds
      6. Steps
        6.1. Givens
        6.2. Whens
        6.3. Thens
        6.4. And, But
      7. Multiline Arguments
        7.1 Tables
      8. Tags
      9. Gherkin in Many Languages

1. Gherkin Syntax

Les tests (feature) Behat sont ecris avec le language gherkin (http://docs.behat.org/en/latest/user_guide/gherkin.html) Il existe plusieurs langues pour ecrire en gherkin comme l'anglais ou le francais Conseil: Il est préférable d'utiliser l'anglais car le français apporte son lot de surprises (Ex. les apostrophe ;-) )

2. Features

une feature doit être ecrite dans un fichier *.feature et doit contenir liste de scénarios (intitulé et description), chaque scénario consiste en une succession d'étapes (step). L'ensemble du contenu du fichier est écrit avec le language Gherkin. Ex.:
Feature: Serve coffee
  In order to earn money
  Customers should be able to
  buy coffee at all times

  Scenario: Buy last coffee
    Given there are 1 coffees left in the machine
    And I have deposited 1 dollar
    When I press the coffee button
    Then I should be served a coffee
                            


3. Scenarios

Chaque scénario consiste en une succession d'étapes (step) avec des mots clés en francais ou anglais au choix. Ex. (en anglais)
  • Given (Première étape du scénario)
  • When
  • Add
  • etc ...
  • Then (Etape finale du scénario)


4. Scenario Outlines

Copier et coller des scénarios pour utiliser des valeurs différentes peut rapidement devenir fastidieux et répétitif: Ex.
Scenario: Eat 5 out of 12
  Given there are 12 cucumbers
  When I eat 5 cucumbers
  Then I should have 7 cucumbers

Scenario: Eat 5 out of 20
  Given there are 20 cucumbers
  When I eat 5 cucumbers
  Then I should have 15 cucumbers
                            
Les Scenario Outlines nous permettent d'exprimer plus précisément ces exemples en utilisant un modèle avec des espaces réservés sans dupliquer les scénarios pour remplacer les entiers: Ex.
Scenario Outline: Eating
  Given there are  cucumbers
  When I eat  cucumbers
  Then I should have  cucumbers
                            
Examples:
| start | eat | left |
|  12   |  5  |  7   |
|  20   |  5  |  15  |
                              
Les Scenario Outlines fournissent un modèle qui n'est jamais exécuté directement. Une structure de scénario est exécutée une fois pour chaque ligne dans la section Exemples située en dessous (sans compter la première ligne des en-têtes de colonne). La structure de Scenario Outlines utilise des espaces réservés, qui sont contenus dans <>

5. Backgrounds

Les Backgrounds vous permettent d'ajouter du contexte à tous les scénarios dans une seule fonctionnalité. Un Backgrounds est comme un scénario sans titre, contenant un certain nombre d'étapes. La différence est quand il est exécuté: Backgrounds est exécuté avant chacun de vos scénarios, mais après vos hooks BeforeScenario (http://docs.behat.org/en/v2.5/guides/3.hooks.html)

6. Steps

Les Features sont composés de Steps, également connues sous les noms de Givens, Whens et Thens...

6.1. Givens

Documentation officielle - http://behat.org/en/latest/

6.2. Whens

Documentation officielle - http://behat.org/en/latest/

6.3. Thens

Documentation officielle - http://behat.org/en/latest/

6.4. And, But

Documentation officielle - http://behat.org/en/latest/

7. Multiline Arguments

Ne pas confondre avec les Scenario Outlines - syntaxiquement, ils sont identiques, mais ont un but différent. Documentation officielle - http://behat.org/en/latest/

8. Tags

Les tags sont un excellent moyen d'organiser vos fonctionnalités et vos scénarios. Les tests de nos features peuvent contenir des annotations @ui ou @javascript Le @ui permettant de dissocier les scénarios qui utiliseront un 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.

9. Gherkin in Many Languages

Gherkin est disponible dans de nombreuses langues, vous permettant d'écrire des histoires en utilisant des mots-clés localisés de votre langue. En d'autres termes, si vous parlez français, vous pouvez utiliser le mot Fonctionnalité au lieu de Fonction. Pour vérifier si Behat et Gherkin prennent en charge votre langue (par exemple, le français), exécutez:
behat --story-syntax --lang=fr
Gardez à l'esprit que tout langage différent de l'anglais (par défault) doit être explicitement marqué avec un: # language: fr commentaire au début de votre fichier * .feature ou dans la commande pour lancer behat behat --lang=fr Documentation Behat: http://docs.behat.org/en/v2.5/guides/1.gherkin.html#backgrounds Tutoriel recommandé: https://www.grafikart.fr/tutoriels/php/behaviour-driven-development-behat-552

Je vous conseille de regarder la vidéo du site Grafikart qui reprend et explique tous les principes de Behat:


Installation de Behat

La première étape est d’installer Behat et ses extensions en tant que dépendance dans notre fichier composer.json Behat permet de "tester les classes" (attention proche du test unitaire, phpunit plus approprié il faut le driver goutte) et les applications web. Pour tester une application web il faut utiliser l'extenssion Mink et ajouter Selenium pour le javascript. Différents drivers doivent être installés pour efféctuer nos tests:

  • Le driver goutte permet de simuler un navigateur (sans utiliser un réel navigateur)
  • Le driver selenium2 ou SAHI (http://www.seleniumhq.org/) est une librairie java qui permet de prendre le contrôle d'un réél navigateur (firefox par exemple) pour simuler une navigation réelle et ainsi tester du javascript
  • L'extenssion Mink 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. Ex.
    • Clicks link with specified id|title|alt|text
    • When I reload the page
    • Given I am on "/"

Il est interessant de regarder la class MinkContext pour créer nos propres méthodes et regarder les bonnes pratiques ;-)

Pour installer SAHI
Téléchargez Sahi depuis http://sourceforge.net/projects/sahi/files/ et exécuter le avec la commande suivante:
java -jar sahi_v35_20110719.jar
Pour changer le port editer le fichier /userdata/config/userdata.properties
et ajouter la ligne (si existe pas)  proxy.port=1024 par exemple
http://127.0.0.1:1024/
cd dossier/installation/bin/
./sahi.sh &
                            


Pour arreter sahi :
ps
kill 
                            


Pour installer Selenium 3 sur Windows ou Linux
Télécharger selenium server (fichier jar)
Installer le driver chrome pour faire tourner selenium sur chrome
https://www.seleniumhq.org/download/
ATTENTION:  selenium 3 est compatible uniquement avec la version 1.8 de java sinon il faut selenium 2.5 (selenium 3 permet de simuler un navigateur autre que firefox)
Lancer le server selenium avec la commande suivante:
cd /folder/where/selenium/jar/file/is/located
java -jar selenium-server-standalone-3.14.0.jar
Pour changer le port editer le fichier /userdata/config/userdata.properties
et ajouter la ligne (si existe pas)  proxy.port=1024 par exemple
http://127.0.0.1:1024/
                            


Pour installer Selenium 3 sur Mac OS X avec homebrew
brew install selenium-server-standalone
brew cask install chromedriver // installer le driver chrome pour faire tourner selenium sur chrome
selenium-server -port 4444
selenium-server -help
                            
Tester que le server Selenium fonctionne, aller sur l'url suivante: http://localhost:4444/wd/hub Je conseille l'utilisation de Selenium plutôt que SAHI. Pour installer la derniere version de Behat et les différents drivers avec Composer utiliser la commande suivante:
composer require behat/behat behat/mink behat/mink-extension behat/mink-goutte-driver behat/mink-selenium2-driver --dev
                            
ou avec SAHI (je recommande d'utiliser Selenium)
composer require behat/behat behat/mink behat/mink-extension behat/mink-goutte-driver behat/mink-sahi-driver --dev
                            


Pour le micro framework Silex une petite extenssion supplémentaire (non testée) "tabbi89/behat-silex-extension" : https://github.com/tabbi89/Behat-Silex-Extension Vérifier que tout fonctionne avec la commande :
path/bin/behat -V
                            


Si tout fonctionne correctement vous pouvez continuer sinon je vous invite à m'envoyer un message par mail. Démarrer un projet Behat
mkdir -p  tests/
cd tests/
path/bin/behat --init
                            
Cela a pour effet de créer les dossiers nécessaires à votre projet:
  • features : les fichiers de fonctionnalité *.feature
  • features/bootstrap : les classes nécessaires au fonctionnement des fonctionnalités (contextes)
  • features/bootstrap/FeatureContext.php : le contexte principal
On verra plus en détail le contenu de tous les fichiers dans la partie fonctionnement. lancer la commande suivante pour lancer les tests:
path/bin/behat
                            
Générer un rapport txt
path/bin/behat/behat --format pretty --out report.txt
                            
Pour plus d'options
path/bin/behat/behat --help
path/bin/behat -dl // On voit toutes les méthodes de nos features( les notres + MinkContext).
                            
Toutes nos étapes sont en attente de définition. En effet, on n'a rien qui fait le lien entre les scénarios (les souhaits du product owner) et notre produit (le code source). Behat nous fournit le code PHP nécessaire pour créer ce lien, avec pour chaque phrase : l'annotation qui permet de faire le lien (@Given /^que je suis un nouveau client$/) la méthode a insérer pour donner du sens à cette phrase (public function queJeSuisUnNouveauClient()) les valeurs de cas du scénario (encadrés par des guillemets), qui sont fournis en paramètres de la méthode Behat nous fourni le code a copier-coller vers la classe qui gère notre contexte principal, à savoir la classe FeatureContext contenue dans /features/bootstrap/FeatureContext.php.



Configuration de Behat

Création à la racine du projet d'un fichier intitulé behat.yml qui va contenir l'ensemble de nos configurations Dans ce fichier on va ajouter tous les drivers à utiliser pour behat: Ex.
selenium2: ~
default:
    extensions:
        Behat\MinkExtension:
            base_url: http://google.fr/
            goutte: ~
            selenium2: ~

Si le serveur selenium est sur une VM remplacer selenium2: ~ par :
Ex.
selenium2: ~
default:
    extensions:
        Behat\MinkExtension:
            base_url: http://google.fr/
            goutte: ~
            selenium2:
                wd_host:http://82.124.1.56:4444/wd/hub
                        
Par défault lorsque l'on créé un nouveau projet et qu'on lance behat --init une feature FeatureContext est créé automatiquement cependant il est interessant pour des grosses applications de découper les contexts.
Dans mon exemple j'ai découpé mon behat.yml en 2 parties:
  • core : qui concerne les fonctionnalités natives les tests sur les classes
  • ui : qui concerne les tests d'applications web
On doit ajouter un filtre pour indiquer à Behat quel context il doit utiliser:

Avant:
suites: 
default:
    contexts: [FeatureContext, BasketContext, WebContext]
                        
Après:
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
Fichier behat.yml complet (exemple à vous de compléter):
default:
extensions:
    Behat\MinkExtension: # Exentession Mink
        browser_name: chrome # navigateur à utiliser pour lancer les tests
        base_url: http://google.fr/ #l'url a tester
        goutte: ~ # expliquer plus haut
        javascript_session: selenium2 #idem pour selenium
        selenium2: ~
suites: #On a plusieurs cas de test découpé en 2 parties core et ui
    core:
        # Two ways to defined contexts - between []
        contexts: [FeatureContext, BasketContext]
        filters: { tags :'@core'}
    ui:
        # Two ways to defined contexts - block defined some specific params impossible with first definition
        contexts:
            - FeatureContext:
            - WebContext:
                # You can also pass arbitrary arguments to the context constructor
                arguments:
                    timer: 3
        filters: { tags :'@ui'}
                        


Documentation officielle

http://behat.org/en/latest/