banner

Rédigé par Aboudou, Développeur chez Soluxan

Contexte


Imaginez que vous débarquez sur un projet avec une application sans tests unitaires, pour laquelle on ne sait plus dire en lisant le code ce qu’elle fait vraiment. Les experts qui avaient la connaissance métier sont tous partis.

Fraîchement arrivé, votre première mission est d’ajouter une fonctionnalité très attendue pour la semaine suivante. Vous faites un état des lieux : l’application fonctionne parfaitement et satisfait les utilisateurs. Les bugs sont fortement évités car ils empêchent les utilisateurs de travailler convenablement et induisent des pénalités financières par heure d’indisponibilité.

Dans un premier temps et avant d’ajouter la nouvelle fonctionnalité, vous décidez de remanier le code existant afin de mieux comprendre ce que fait l’application.

Cette première tâche s’annonce compliquée, car il n’y a pas de gardes de sécurité pour s’assurer que vous n’allez pas ouvrir la porte aux terribles bugs. De plus, on vous demande de ne pas recourir à des tests unitaires car cette tâche prenait trop de temps aux développeurs qui vous ont précédé.

Ce genre de situation est commun et très inconfortable. On se sent prisonnier du code et on craint de le modifier de peur d’introduire des bugs.

Une approche possible (car il en existe d’autres en fonction du contexte) est d’introduire des tests de caractérisation.

Comment ça marche ?


Les tests de caractérisation vont nous permettre de capturer le comportement de l’application lorsque l’on sait qu’elle est fonctionnelle et que la source est fiable, même si on a encore du mal à comprendre le code actuel.

On retrouve souvent ce genre de tests sous le nom de golden master test ou snapshot test.

En fait, c’est comme prendre des photos du comportement de l’application en simulant son utilisation par un vrai utilisateur. On écrit un test pour lequel :

  • On injecte des données en entrée à partir de cas métiers
  • Dans la partie assertion, on indique une valeur au hasard car on ne maîtrise pas encore le résultat attendu
  • On lance le test et lorsque sans surprise celui-ci échoue, cela nous indique la valeur qui était attendue
  • On utilise cette valeur attendue dans la partie attendue de notre assertion.
  • En rejouant le test, ce dernier devrait fonctionner

Ainsi, nous obtenons un premier test de caractérisation nous donnant plus de détails sur le comportement de l’application dans un contexte donnée. Il reste à réitérer ce procédé plusieurs fois afin de capturer au maximum le comportement actuel du code.

Pour s’assurer que ces tests de caractérisation sont assez robustes pour monter la garde et éviter que des bugs s’introduisent dans votre application, deux étapes s’imposent :

  • Premièrement, vérifiez la couverture du code. Les tests doivent permettre de couvrir la partie de code que vous vous apprêtez à changer.
  • Dans une seconde phase et via des tests de mutations, assurez-vous qu’ils seront capables de détecter des bugs si des intrus (mutants) pénètrent l’application. Pour cela vous pouvez introduire des erreurs évidentes pour vérifier la qualité de la couverture, en commentant un bout de code, en inversant une condition…

Concrètement


La librairie Approvals, disponible dans plusieurs langages de programmation (C++, C#, Java, Ruby, Python, Php, NodeJS, etc..) simplifie la mise en œuvre des tests de caractérisation.

Prenons un exemple très simple pour illustrer la démarche. Supposons que le but de l’application est de retourner une salutation sous forme de chaîne de caractères à partir d’une autre donnée en entrée :

class Greetings {
public static String greet(String name) {
return "Hello, " + name + "!";
}
}

Pour créer un test de caractérisation avec Approvals, nous pouvons utiliser la méthode verify de la librairie, qui pourrait ressembler à ceci :

public class GreetingsCharacterizationTest {
@Test
void greet_with_input_bob() {
String name = "Bob";
String greeting = Greetings.greet(name);
Approvals.verify(greeting);
}
}

Lorsque vous exécutez ce test pour la première fois :

  • Approvals enregistre le résultat dans un fichier temporaire avec l’extension « .received.txt« . Dans notre cas : GreetingsCharacterizationTest.greet_with_input_bob.received.txt.
    Assurez-vous de ne pas versionner les fichiers temporaires « .received », en ajoutant par exemple l’instruction *.received.* dans votre fichier .gitignore.
  • Ensuite, Approvals cherche à comparer le contenu de ce fichier avec celui d’un fichier dont le résultat est approuvé « .approved.txt« . Ici, cela donne : GreetingsCharacterizationTest.greet_with_input_bob.approved.txt
  • Le fichier approuvé ne contient rien pour l’instant et le test échoue
(Capture 1 – Fichiers générés lors de la première exécution du test)

NB: La librairie offre la possibilité de modifier l’emplacement ainsi que le format des fichiers générés.

Ensuite, puisque nous sommes sûrs que l’application fonctionne comme attendu, nous approuvons en copiant le contenu du fichier « received » dans le fichier « approved ». En général, votre IDE vous aide en montrant la différence entre les deux fichiers.

(Capture 2 – Comparaison entre le fichier received et le fichier approved)

A la prochaine exécution :

  • Le fichier received contiendra le même contenu que le fichier approved, et notre test passera au vert  \o/.
  • le fichier received disparaîtra automatiquement et il ne restera que le fichier approved

Dorénavant chaque nouvelle exécution du test comparera le résultat actuel avec le résultat approuvé. C’est cette comparaison qui permettra de détecter les éventuels bugs apparaissant pendant le remaniement de code à venir.

Il est temps de répéter ce processus avec différents cas pour couvrir le plus de scénarios possibles. Une fois que l’on a cette batterie de tests, nous sommes armés pour faire notre refactoring de code afin de le rendre plus lisible.

Bien que l’exemple utilisé soit très simple, il suffit de projeter la démarche équivalente sur une base de code compliquée.

Conclusion


Les tests de caractérisation constituent une solution efficace et adaptée dans le cas de travail sur un code inconnu. Ils permettent d’obtenir rapidement un filet de sécurité pour une application que l’on ne comprend pas exactement.



Cet article a pour objectif de vous présenter les tests de caractérisation avec la librairie Approvals. Pour aller plus loin sur le sujet, nous vous proposons les ressources suivantes :

  • Le site officiel de la librairie Approvals https://approvaltests.com/.
  • Si vous aimez la lecture, nous recommandons le livre « Working Effectively with Legacy Code » de Michael Feathers, qui aborde les tests de Characterization au Chapitre 13.
  • Pour vous entraînez, vous pouvez vous essayer de mettre cela en application sur le kata « Gilded Rose »(https://kata-log.rocks/gilded-rose-kata).Vous trouverez en ligne plusieurs blogs avec différentes implémentations utilisant la librairie Approvals.

Laisser un commentaire

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