[FR] TDD: Les 3 choses que tu peux tester


Quand tu commences à faire du TDD, c’est normal d’être un peu paumé.
Tu ne sais pas trop quoi tester, tu avances à tâtons, et si en plus tu découvres le framework de test en même temps parce que t’en as jamais fait… c’est le combo parfait pour galérer pendant plusieurs heures.
Mais y’a des petits trucs simples à retenir qui peuvent t'aider. Ça va pas régler tous tes soucis, certes, mais ça peut clairement te débloquer quand tu ne sais pas trop par où commencer.
Parce qu’au fond, si tu prends deux secondes pour y penser, à part quelques cas très particuliers (tests de perf, de sécu, etc.), y’a que trois manières de vérifier qu’un bout de code fait ce que tu attends de lui.
Pas dix, pas cinquante. Juste trois.
Et une fois que tu as compris ça, tout devient beaucoup plus simple.
La seule question à te poser
Commence juste par répondre à cette simple question:
Est-ce que le résultat que je veux vérifier est direct, indirect ou une collaboration?
Bon là je vois déjà ta tête:
“Oula c’est quoi ces trucs, je comprends rien, laisse tomber…”.
→ T’inquiètes pas on va voir ça ensemble avec des exemples.
Direct output
On commence avec le plus simple. Et ça tombe bien parce que c’est celui que tu vas essayer d’utiliser le plus souvent. C’est aussi celui qui va te paraitre le plus naturel.
Imagine que le code que tu essaie d’écrire est celui d'une “machine”. On ne sait pas ce qu’il y a à l’intérieur.
Cette machine, tu lui as donné un but. Ici son but est de “créer une potion à partir d’une herbe”.
→ Donc ce que la machine
va “recevoir en entrée”, ce sont des herb
es.
→ Ce qu’elle “renverra en sortie” c’est une potion
.
Si tu traduis ça en test, vu qu’on est en train de faire du TDD, tu pourrais imaginer quelque chose comme ça (avec des strings
pour rester simple):
it('should create a green potion from green herb', () => {
// Arrange
const machine = new Machine();
const herb = 'green-herb';
// Act
const potion = machine.createPotion(herb);
// Assert
expect(potion).toBe('green-potion');
});
Tu vois, on retranscrit assez facilement ce qu’on a dit juste au-dessus. On passe une herb
en entrée, et en retour on reçoit une potion
verte. Le résultat est direct.
C’est le cas le plus simple à tester car tu vas pouvoir passer des valeurs différentes en entrée et checker le résultat en sortie assez facilement.
C’est souvent le cas des calculs d'ailleurs.
Quand on calcule la somme de deux nombres, c’est assez naturel de se dire “si je passe 1
et 2
en entrée
, le result
doit être égal à 3
”.
it('should make the sum of two numbers', () => {
// Arrange
const a = 1;
const b = 2;
// Act
const result = sum(a, b);
// Assert
expect(result).toBe(3);
});
Bref, le truc c’est de se dire que, si le code que tu souhaites tester renvoie quelque chose, c’est du “direct output” et c'est facilement testable.
Indirect output
Parfois ça peut être un peu plus compliqué. Le résultat peut être disponible mais “ailleurs”. Et donc pour écrire ton test, tu vas devoir prendre en compte cette contrainte toi aussi. Reprenons notre exemple avec une petite subtilité.
Notre machine est reliée à une lampe qui devient verte quand la potion a bien été créée.
Dans notre cas, la lampe est externe à la machine. La machine y accède d’une façon ou d’une autre, et déclenche un changement d’état.
Comment elle fait? On s’en fiche, tout ce qu’on sait c’est que la machine ne “renvoie pas” une lampe en sortie… C’est donc un “indirect output”.
Si tu traduits ça en test, une solution possible serait:
it('should change the color of the lamp to green', () => {
// Arrange
const lamp = new Lamp();
const machine = new Machine(lamp);
const herb = 'green';
// Act
machine.createPotion(herb);
// Assert
expect(lamp.color).toBe('green');
});
On lit facilement que la machine
a accès à la lamp
, et qu’après avoir créé la potion
, la lamp
devient verte.
Mais contrairement à la version direct output
, ça demande une petite gymnastique mentale en plus.
Et voilà pour notre deuxième façon de tester.
Encore une dernière, et on a fait le tour.
Collaborateurs
Suivant les tests que tu souhaites faire, parfois tu vas vouloir vérifier que les choses se déroulent exactement comme tu le souhaites.
Ce ne sera pas un résultat qui sera checké, mais un comportement.
On va vouloir vérifier que les acteurs (qu’on va appeler collaborateurs) remplissent bien leur rôle comme il faut. Voici un exemple.
La lampe doit clignotter 3 fois pour avertir qu’une nouvelle potion est prête.
Plusieurs choses ici.
Allumer/éteindre une lampe, c’est peut-être long. Ça consomme de l’énergie, ça use l’ampoule.
Bref, c’est pas un truc qu’on veut faire pour de vrai en test.
Le mieux serait d’utiliser un “test double” ou “doublure de test” en francais pour éviter ces problèmes.
Ce qu’on veut ici, c’est s’assurer que la méthode blink()
a bien été appelée 3 fois.
it('should make the lamp blink 3 times', () => {
// Arrange
const lamp = new Lamp();
spyOn(lamp, 'blink').mockImplementation();
const machine = new Machine(lamp);
const herb = 'green';
// Act
machine.createPotion(herb);
// Assert
expect(lamp.blink).toBeCalledTimes(3);
});
✋ Hophophop! T’as plein de manières de faire des doublures de tests.
Ici je retranscrits uniquement ce que nous avons dit au dessus.
J’ai utilisé le spyOn avec mockImplementation (dispo dans jest & vitest) dans cet exemple car je pense que tu as déjà dû les rencontrer dans la doc ou des tutos.
Mais ce n’est clairement pas la seule facon de faire.
On verra notamment comment créer nos propres doublures sans librairie et les différences entre chaque type de doublure (dummy, stubs, spy, fakes etc…). Mais en attendant, on va rester sur cette proposition.
Ici, tu ne vérifies pas un état mais plutôt que ton collaborateur a bien été sollicité comme prévu pour remplir son job.
→ Tu valides donc un comportement.
Ce que tu dois retenir
Quand tu veux écrire un test, demande toi si tu veux vérifier:
Ce que le code produit (direct)
Ce qu'il change ailleurs (indirect)
Ce qu'il fait faire aux autres (collaboration)
Pour aller plus loin
Bien entendu les termes ici ont été simplifiés. Mais ça devrait t'aider à y voir plus clair pour écrire tes prochains tests.
Si tu souhaites pousser plus loin, tu peux retrouver tout ça sous les noms:
Direct → State Verification
Indirect → Observable Side Effect, Indirect Output
Collaboration → Interaction Testing, Behavior Verification
→ Mais t’inquiètes pas, on verra tout ça un peu plus dans les prochains articles.
Voilà quelques livres incontournables sur le TDD (liens d’affiliation Amazon):
xUnit Patterns (Gerard Meszaros): https://amzn.to/42pw3hZ - Assez vieux mais l’un des meilleur concernant les patterns de tests
TDD by Example (Kent Beck): https://amzn.to/3YvZu0G
Dans un prochain article, on parlera des inputs directs et indirects donc reste à l'écoute.
Subscribe to my newsletter
Read articles from Evyweb directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
