Installation, configuration et fonctionnement des tests fonctionnels avec Behat
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'}