Behat et SymfonyPage

Behat et Mink

Behat est un outil de test fonctionnel dont les scénarii sont écrits en langage commun grâce à Gerkin.
Le concept est simple, le test est divisé en 3 parties « Étant donné que » pour donner les conditions préalables, « Lorsque » pour indiquer les actions réalisées pour le test, puis « Alors » pour vérifier que les actions réalisées ont produit le résultat souhaité.

Chaque ligne du test est une phrase complète qui commence par un mot-clé. Dans cette phrase, des éléments peuvent servir de données utiles pour le test.

Chaque phrase est associée à une méthode PHP grâce aux classes de contexte. La phrase est située dans une annotation au-dessus de la fonction à appeler. L’annotation peut contenir une expression rationnelle ou une phrase avec des placeholders commençant par deux points. Chaque parenthèse de capture de l’expression rationnelle ou placeholder sera un argument de la méthode PHP appelée pour exécuter la phrase du test.

Ex :

/**
 * @Then je dois voir le texte :texte
 */
public function jeDoisVoirLeTexte($texte)
{
}

C’est ainsi que les tests peuvent être exécutés.

Afin de ne pas se répéter, les contextes sont réutilisables.

Pour tester des pages Web HTML, l’utilisation de Mink est nécessaire. Il dispose de pilote (driver) pour différentes situations.

En voici quelques-uns, chacun avec ses avantages et inconvénients :

  • Sans serveur Web et Navigateur (extension Symfony) : Simule un objet Request et récupère l’objet Response.
  • Avec un serveur Web, mais sans Navigateur (extension Goutte) : Réalise de vraies requêtes HTTP via un client HTTP sur un serveur Web et obtient une réponse HTTP. Seul le JavaScript n’est pas exécuté.
  • Avec un serveur Web, mais avec un Navigateur (extension Selenium) : Pilote un navigateur Web sans fenêtre pour réaliser les actions sur le site Web et vérifier le résultat. Ces tests sont particulièrement longs à exécuter.

Pour éviter l’écriture de fonctions complexes dans les contextes, Mink abstrait les drivers. Le cas des tests avec JavaScript est particulier, car le pilote est impatient et il génère de faux échecs de test. L’ajout de délais dans les fonctions du contexte est nécessaire.

C’est ici que les choses se compliquent. Eviter l’écriture de contexte est possible grâce au contexte proposé par Mink. Cependant, vos tests ne ressembleront pas à de vrais tests fonctionnels, car ils contiendront des classes CSS, des xPath dans vos phrases.

Un autre point est qu’une phrase ne peut être déclarée qu’une seule fois et chaque contexte est indépendant des autres. Cela bloque l’héritage des contextes par défaut de Mink pour un seul contexte. Votre contexte gonflera pour contenir tous les cas d’actions sur les pages.

Concept de PageObject

Comment diviser cet énorme contexte en contextes plus petits et surtout mieux organisés ? Comment permettre l’écriture d’un contexte par page de l’application ou par concept ?

La première approche serait d’utiliser le RawMinkContext qui apporte le strict minimum des dépendances nécessaires pour écrire des cas de test et d’actions.
Cependant, mutualiser du code utile pour plusieurs phrases ne sera pas possible. Pour cela, l’utilisation des services externes au contexte est nécessaire.

Le pattern PageObject est venu résoudre ce problème. Nous pouvons en profiter dans Behat soit par l’extension SensioLab soit par l’extension du groupe Friend of Behat.

Mais qu’est-ce donc ?

Ce sont des services qui ont une dépendance vers Mink et qui permettront de réaliser des actions sur la page actuellement chargée. Un service PageObject permet de regrouper toutes les actions possibles d’une page ou d’un élément commun du site (le concept d’Element est plus adapté dans ce cas).

Le service PageObject expose les actions disponibles et les données à récupérer sur la page pour laquelle il est dédié. Il ne fait pas d’assertion (ou très très peu).

Ce service est ensuite injecté dans le service de contexte afin de pouvoir interagir avec la page.

L’extension proposée par Friends Of Behat propose un concept de SymfonyPage qui utilise le routeur Symfony pour appeler la page (donc, générer l’URL) et vérifier que le service PageObject va réaliser une action sur la bonne page.

C’est donc dans ce service PageObject que nous allons manipuler les éléments techniques de la page Web.

Conséquence dans les Context

La première conséquence est la possibilité de séparer les définitions des phrases en plusieurs contextes indépendants.

La seconde conséquence est la possibilité d’organiser les contextes en fonction des modules de l’application et sortir ceux relatifs à l’infrastructure telle que les hook Behat et les contextes liés à la préparation de la base de données pour le test.

Ainsi, voici une architecture possible pour le dossier de tests fonctionnels :

tests
└── Functional
    ├── Context
    │   ├── Admin
    │   │   ├── LoginContext.php
    │   │   ├── NotificationContext.php
    │   │   ├── Module1
    │   │   │   └── Module1Context.php
    │   │   └── Module2
    │   │       ├── IndexModule2Context.php
    │   │       └── ShowModule2Context.php
    │   └── Module2
    │       ├── DashboardContext.php
    │       └── LoginContext.php
    ├── Hook
    │   └── FixtureContext.php
    ├── Page
    │   ├── Admin
    │   │   ├── Account
    │   │   │   └── LoginPage.php
    │   │   ├── Module1
    │   │   │   └── IndexPage.php
    │   │   └── Module2
    │   │       └── ShowPage.php
    │   └── Module2
    ├── Setup
    │   └── MonEntiteContext.php
    └── Transform
        └── QuantityContext.php

Certains auront peut-être trouvé la source d’inspiration de cette organisation. C’est celle utilisée dans les tests du projet de e-commerce Sylius.

Enfin, une conséquence non négligeable est la nécessité d’écrire plus de fichiers PHP et de code pour réaliser les tests. Mais cette conséquence disparaît vite avec la mise en pratique.

Bénéfices

Les utilisateurs réguliers des tests fonctionnels avec Behat et Mink seront sûrement septiques. Mais n’avez-vous jamais eu besoin de dupliquer du code pour vérifier qu’une notification est bien présente et contient le texte souhaité ?

Cette façon de faire répond exactement à ce cas. Vous pouvez définir un service PageObject (ou plutôt Element dans ce cas) qui se concentrera sur la vérification des différentes notifications possibles. Vous l’injecterez dans tous les contextes qui en ont besoin dans l’exécution d’une phrase. Cela évitera des phrases comme « je dois voir la notification… » qui ne sont pas forcément utiles.

Un autre bénéfice des PageObject est de pouvoir injecter le service dans plusieurs contextes et ainsi éviter la duplication de code.

à vous de jouer !

Qu’en pensez-vous ? Comment réalisez-vous vos tests fonctionnels ?

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

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