25/02/2013

Behat - créer des tests solides et efficaces

Ca commence à faire déjà quelques temps que j'utilise Behat, et j'ai eu la chance de pouvoir l'utiliser sur différents projets, gros et petits. Je commence donc à avoir un peu plus de recul sur la chose, et à avoir accumulé pas mal de mauvaises pratiques.

Par manque de chance (ou pas ^^), il a fallu que je commence à utiliser Behat sur un projet assez important. C'est donc là que j'ai commencé à faire mes plus grosses erreurs. Je vous propose de voir quelles ont été ces erreurs, et comment éviter de les reproduire.

Utiliser Behat sans faire du Développement piloté par le comportement

C'est évident, mais il faut le dire : Behat n'est qu'un outil, ce qui compte vraiment c'est le Développement piloté par le comportement. Et oui ! Behat n'est ni un outil de test, ni un outil de spécification. Ce n'est "rien de plus" qu'un bonus par rapport à une démarche de travail.

Si on prend Behat, comme il m'est arrivé de le faire, comme simple outil de test, on fonce dans le mur : au bout de plusieurs mois on risque d'avoir substitué Behat à des outils de tests unitaires (PHPUnit, atoum). Behat permet (entre autres) de tester du besoin fonctionnel. Encore faut-il que ce besoin soit exprimé !

Faire du refractoring de code plutôt que du refractoring de phrases

Allez, créons une méthode privée réutilisable dans notre Contexte de définition. Ah, et puis passons la publique le jour où il nous faudra l'utiliser dans un autre Contexte...

Au final on se retrouve avec une application dans l'application. Quand c'est le cas, et ça m'est arrivé, c'est qu'on confond refractoring de code et refractoring de définitions.

Les Contextes de définition servent à traduire des expressions en code source. C'est donc sur ces expressions qu'il faut se focaliser. L'idéal est de réussi à parvenir à découper chaque expression en unités atomiques fondamentales.

Par exemple :

Quand que j'ajoute dans mon panier "Télévision Sony" depuis le catalogue produits

On peut découper cette expression en sous-expressions (étapes)

Etant donné que je suis sur la page du catalogue produit
Quand j'ajoute "Télévision Sony" au panier

Et au final on peut arriver à des expressions atomiques simples :

Etant donné que je suis sur "/catalogue/produits"
Quand je sélectionne "Télévision Sony"
Et je clique "Ajouter au panier"

Vous voyez que là on fait du refractoring de phrases. L'avantage est que c'est hyper simple avec Behat :

<?php
use Behat\Behat\Context\Step;

/**
* @Given /^que je consulte le catalogue produit$/
*/
public function queJeConsulteLeCatalogueProduit()
{
	return array(
	    new Step\Given('que je suis sur la page "/catalogue/produits"')
	);
}

// etc

Il suffit d'utiliser des sous-appels d'étapes dans la définitions.

C'est la seule manière que j'ai trouvé pour se retrouver avec des Contextes de définition simples et surtout stables dans le temps. Sinon on passe notre temps à les réécrire dans leur ensemble.

Par contre ça oblige à avoir plein de définitions, d'où le point suivant :

Utiliser Behat sans couche d'isolation

Au départ, quand on utilise Behat, on a tendance à se créer 2 ou 3 contextes de définition, et puis c'est tout. C'est à mon avis une erreur. Aujourd'hui, je démarre chaque projet qui utilise Behat avec au moins ce découpage :

  • Contextes de définition métiers
  • Contexte de définition de vue (web)
  • Couche d'isolation pour la persistance des données si besoin

Pourquoi ? Tout simplement parce que je me suis rendu compte d'une chose : il arrive souvent de devoir repasser sur des définitions ! Et oui. Et c'est long, laborieux et, disons-le, très très démotivant.

Il faut au moins regrouper les définitions qui concernent l'interface graphique dans un contexte spécifique. Par défaut, si vous utilisez Mink, c'est ce qui fait plus ou moins implicitement lorsque vous utilisez des définitions toute-prêtes de Mink.

Mais que se passera t-il quand le client vous annoncera qu'il sort une appli mobile ? Normalement, appli mobile ou web, aucune spécification ne devrait changer. Par contre, si vous avez mal organisés vos Contextes de définition, vous allez galérer.

Au contraire, si vous regroupez tout ce qui concerne l'UI dans un Contexte de vue, mettons par exemple "View\WebContext.php", il vous suffit simplement de gérer vos sous-contextes :

public function __construct(array $parameters)
{
    if ($parameters['view'] == 'mobile') {
        $this->useContext('view', new View\MobileContext($parameters));
    } else {
        $this->useContext('view', new View\WebContext($parameters));
    }
}

C'est aussi simple que ça : il vous suffit de regrouper tout ce qui concerne l'affichage dans un Contexte spécifique.

Bien entendu, pour les très grosses applications on peut aller plus loin, et d'ailleurs ça devient très vite intéressant : créer un Contexte par page. Par exemple:

$this->useContext('view.catalogue', new View\Web\CatalogueContext($parameters));
$this->useContext('view.panier', new View\Web\PanierContext($parameters));

Ca peut paraître extrémiste, mais dans le cas où votre application est conséquente, c'est très utile. Il faut bien se dire que tout ce travail consiste à solidifier votre architecture de test. De la même manière qu'il faut découper une application en blocs de code (modules), il faut découper les Contextes de définition selon leur... contexte.

Après, il faut prendre la juste mesure des choses, et trouver un découpage adapté :

  • aux besoins
  • à la taille du projet
  • au temps disponible
  • à la compétence de l'équipe

Exactement comme on le ferait pour un applicatif.

Voilà pour ces quelques retours que je souhaitais partager avec vous. Après, ils sont basés sur mon expérience personnelle, et je serai très curieux de savoir ce que vous en pensez, et surtout comment vous vous avez fait pour gérer la croissance de vos projets utilisant Behat / Cucumber / etc.. N'hésitez pas à partager votre expérience...

blog comments powered by Disqus