Cet article est un extrait du livre sur JUnit
Qu’est-ce qu’un bouchon ?
Lors de sa conception, un système logiciel est décomposé en modules ou sous-parties ce qui permet de simplifier le travail à faire. Lors de la fabrication de ces modules, les développeurs les valideront à l’aide de tests unitaires. Cependant, le module en cours de fabrication sera très souvent dépendant d’autres modules.
Dans le schéma ci-dessus, le module III :
- dépend en amont des entrées fournies par les modules I et II ;
- doit alimenter en aval les modules IV et V.
Ces dépendances peuvent compliquer l’écriture de tests automatiques, voire la rendre impossible dans certains cas. Par ailleurs, ces dépendances rendent les tests vulnérables aux évolutions des modules aussi bien en amont qu’en aval. Ces contraintes risquent d’éroder la motivation des développeurs au point de mettre en péril l’écriture des tests automatiques. Dans ces cas, l’utilisation de bouchons permet de passer outre ces limitations.
Un bouchon est un élément qui peut se substituer à un autre afin d’en imiter le comportement. Cette imitation peut être plus ou moins sophistiquée. On distingue notamment différents types de bouchons.
- Les bouchons statiques: ils sont très simples et simulent le fonctionnement d’une classe de façon passive. Typiquement, l’appel d’une méthode retournera toujours la même valeur, quels que soient les paramètres.
- Les bouchons dynamiques: ils intègrent une intelligence simple. Ils sont typiquement capables de prendre en compte des paramètres afin d’adapter leur réponse.
- Les bouchons intelligents: ils simulent le fonctionnement de l’élément remplacé. Ils prennent en compte les paramètres d’entrée et adaptent leur réponse comme le ferait la classe d’origine.
Selon l’objet testé, il sera plus approprié d’utiliser de nombreux bouchons statiques peu complexes qui simuleront chacun un comportement précis ou un seul bouchon intelligent qui simulera le sous-système complet, mais qui sera complexe à développer.
Quand utiliser les bouchons ?
Comme nous l’avons évoqué, l’utilisation des bouchons est particulièrement indiquée lorsque l’écriture des tests unitaires sur une classe devient compliquée. En particulier lorsque :
- Le sous-système est lent : lorsque le sous-système ralentit l’exécution des tests unitaires, il peut être avantageux de le remplacer par un bouchon intelligent qui permet d’accélérer les tests. Il peut s’agir par exemple d’un système distant ou ayant une capacité de traitement limitée.
- Le sous-système fonctionne de façon non déterministe : c’est typiquement l’exemple de fonctions ayant une notion de temps ou utilisant des marquages temporels : par exemple une classe de journalisation.
- Le sous-système utilise une interface utilisateur bloquante : c’est par exemple le cas d’un module qui a besoin de saisies de la part de l’utilisateur.
- L’objet réel n’existe pas encore : un cas plus fréquent qu’il n’y paraît a priori. Même si l’implémentation est triviale, il peut être difficile d’accepter de mobiliser de l’énergie pour construire un bouchon au lieu de fabriquer le module réel. Pourtant bien souvent le gain final est important. Par exemple dans une démarche d’écriture des tests préalables à l’implémentation, cela peut avoir un sens si les tests et le code sont développés par des équipes différentes : le bouchon permettra de valider la campagne de tests en attendant le code réel.
- Le sous-système est difficile à initialiser : lorsque le sous-système nécessite une initialisation complexe, il peut être avantageux de passer par un bouchon. C’est typiquement le cas de tests de montées en charge qui nécessitent l’utilisation de comptes utilisateurs prédéfinis : si le test utilise plusieurs milliers de comptes, la création de ces comptes peut devenir problématique.
- Les mises en erreur ne sont pas reproductibles : en effet, les cas d’erreur d’un sous-système peuvent être difficiles à reproduire, rendant ainsi le test du système impossible à contrôler. C’est typiquement le cas pour la disponibilité d’un serveur Web ou d’une base de données. Dans ces cas, des bouchons statiques produisant systématiquement l’erreur voulue seront très adaptés.
- Les effets réels ne sont pas souhaités : c’est typiquement le cas d’une API bancaire. Dans ce cas il est souhaitable de tester la procédure de paiement sans débiter réellement la carte bleue.
D’une manière générale, l’utilisation des bouchons permet de simplifier l’écriture des tests unitaires. Cependant, l’effort d’écriture de ces derniers peut devenir important, en particulier dans le cas de bouchons intelligents. Il faut donc toujours être vigilant à ce que cet investissement soit justifié. En particulier, un bouchon utilisant un autre bouchon doit être exceptionnel et interprété comme le signe d’une complexification qui devrait alerter les développeurs.
Afin d’utiliser un bouchon, il faut distinguer leur fabrication à proprement parler de la possibilité de les mettre en œuvre. Nous commencerons par décrire ce dernier point avant de voir le premier au travers d’un exemple détaillé.
La suite dans le livre sur JUnit