J'ai l'impression d'écrire mes tests en double !
En présentant les tests fonctionnels automatisés chez un client la semaine dernière, plusieurs questions ont été soulevées. La principale était celle-ci:
- Pourquoi écrire ces tests FitNesse/GreenPepper alors que j'ai déjà des tests unitaires JUnit qui couvrent la même fonctionnalité ?
La question est justifiée. Voici quelques éléments de réponse, tirés de nos échanges sur les mailing-lists OCTO...
Des tests différents pour des acteurs différents
Tout d'abord, parce que les tests unitaires et les tests fonctionnels sont écrits par des personnes différentes.
Les tests unitaires sont écrit et utilisés par les développeurs. Ils sont codés dans le même langage que l'application testée, leur exécution est rapide, ils sont régulièrement refactorés, modularisés, etc. Ils sont non négociables (!) et doivent tester l'intégralité du code applicatif.
Mais ces tests unitaires ne sont pas appréhendables par les fonctionnels (client, MOA, AMOA, etc.)
Et c'est là qu'interviennent les tests fonctionnels automatisés: ils sont écrits par les fonctionnels et sont "lisibles par un humain".
Finalement, "s'il y a des doublons, c'est une coïncidence, en quelque sorte :)".
Idée: On a vraiment des doublons, lorsque, sur un projet, ce sont les développeurs qui font aussi les tests fonctionnels et qu'ils sont les seuls à les lire ou les mettre à jour. Dans ce cas, il est intéressant de se poser la question de l'utilité de ces tests: il y a fort à parier qu'on pourrait se contenter de tests développeurs JUnit.
Des différences de granularité
Les tests unitaires et les tests fonctionnels sont également différents dans leur granularité: les tests unitaires sont "boite blanche" (c'est à dire qu'ils testent le code... "ils regardent sous le capot")...alors que les tests fonctionnels sont "boite noire" (ils testent l'application de bout en bout). Si les tests se ressemblent trop dans leur structure ou leurs assertions, c'est probablement que leur granularité est à retravailler.
Idée 1: On rencontre sur beaucoup de projet des tests "boite noire" développés avec JUnit: ce sont les tests d'intégration. Ils passent par plusieurs services, DAOs, par la base de données, etc. Ces tests sont utiles car ils permettent de tester la communication entre les couches, la configuration, etc. Mais ils sont généralement lents à exécuter. Il faut donc les utiliser avec précaution ! Deux raisons possibles à l'utilisation massive de ces tests d'intégration: ou bien le projet ne dispose pas d'outils dédiés aux tests "boite noire" (FitNesse, GreenPepper, Selenium, etc.), ou bien ceci est dû à une méconnaissance des outils de Mock et des pratiques associées. Quelle qu'en soit la cause, limiter le nombre de ces tests "d'intégration" au profit de tests plus "unitaires" permet de réduire considérablement la durée globale d'exécution des tests. Et avoir des tests rapides, cela permet de les exécuter souvent, et donc de détecter les anomalies plus tôt.
Idée 2: Séparer les tests unitaires (rapides) des tests d'intégration (lents) est une bonne pratique. Ceci peut se faire par convention de nommage des classes de test, répertoires séparés, annotations ou autre. Une fois les tests JUnit catégorisés, des outils comme Maven permettent de définir des règles du type "les tests unitaires se lancent sur le poste développeur, les tests d'intégration s'exécutent uniquement sur le serveur d'intégration continue". Cette séparation permet d'éviter le travers (récurrent) suivant: "les tests sont trop longs, je ne les exécute pas localement et les délègue à l'intégration continue".
Des objectifs différents et complémentaires
Enfin, et c'est le point le plus important, les tests unitaires et les tests fonctionnels automatisés poursuivent des objectifs complémentaires:
- Les tests unitaires
- favorisent la collaboration entre les développeurs
- améliorent la productivité des développements
- assurent un harnais de test permettant le refactoring "sans stress"
- Les tests fonctionnels automatisés, quant à eux
- permettent de spécifier le produit en un endroit/format unique en détaillant les subtilités qui n'apparaissent pas dans les simples User Stories, avec des données de test qui ont un sens "métier"
- favorisent la communication entre le client et l'équipe de développement
- représentent un harnais de test garantissant la non-régression fonctionnelle
A retenir
Nos projets intègrent plusieurs types de tests. Parmi eux, les tests unitaires et les tests fonctionnels couvrent les mêmes fonctionnalités, il est donc normal d'avoir le sentiment qu'ils se recoupent parfois.
Cependant, ils sont écrits par des personnes différentes, avec des niveaux de granularité différents et, surtout, des objectifs complémentaires: - Faire bien le produit, pour les uns - Faire le bon produit, pour les autres
En revanche, c'est au niveau des tests unitaires ou d'intégration qu'il est important de commencer la chasse aux doublons ! La durée de votre build ne s'en portera que mieux!
Un grand merci à Ismaël Héry, Julien Jakubowski, Eric Pantera, Arnaud Huon, Rémy-Christophe Schermesser, Christophe Thibault, Henri Tremblay, Maria-Dolores Manzanedo, Sylvain Fagnent, Bertrand Paquet, Sylvain Mazaleyrat et Florian Marin pour ces échanges et retours d'expérience enrichissants !