🤔 Pourquoi écrire des tests automatisés ?
Revoyons les bases des tests automatisés dans cette série d'articles :)
Cet article fait partie d’un groupe d’articles à propos des tests automatisés. Celui-ci est le premier article de la série :)
“N’oublie pas de tester ton code avant de le livrer en production” !
On a tous entendu cette phrase au moins une fois. Elle tombe sous le sens, qui serait assez inconscient pour livrer du code en production sans l’avoir testé au préalable ?
Le CDD : Console.log Driven Development
Comment s’y prend-on pour tester le tout nouveau code que l’on doit livrer ? La première chose qui vient en tête, c’est de simplement exécuter le code pour voir s’il produit bien le résultat attendu.
On va donc lancer le navigateur si l’on travaille sur du frontend, ou lancer éventuellement le script en console en backend / faire un appel Postman ou Insomnia à une API, pour s’assurer du résultat.
Ça ne fonctionne évidemment jamais du premier coup, donc on passe en phase de débogage intensif !
Bien souvent à grand renfort de console.log(“ICI”)
pour s’assurer que le code passe bien dans une boucle que l’on veut, ou encore, frustré que la variable toto
ne contient pas la bonne valeur : console.log(“POURQUOI CA MARCHE PAAAAAS ?”, toto)
Bref, on fait du Console.log Driven Development…
Mais au fait, qu’est-ce que ça veut dire “tester” son code ?
Cette démarche de débogage a au moins le mérite de mettre en évidence un “algorithme” de résolution de problème. Celui-ci peut se résumer ainsi :
notre système est dans un état initial connu
on cherche à exercer une action sur ce système initial (en accédant à une page, validant un formulaire, faisant un appel API, etc.)
on constate un problème : notre site n’affiche pas ce qu’il faut, notre API ne retourne pas les bonnes valeurs
on cherche le problème par petites itérations afin de cerner le bug en réduisant le périmètre de code où il pourrait se trouver
on résout le bug
on croise très fort les doigts pour ne plus jamais le rencontrer à l’avenir
Il y a de bonnes choses dans cet algorithme de résolution de problèmes, mais aussi de très mauvaises.
Commençons par les bonnes. Cet algorithme a pour intérêt principal d’être répétable à l’infini. Pour n’importe quel problème rencontré, la démarche sera la même :
d’abord prendre conscience de l’état initial du système
effectuer une action sur ce système
constater le problème éventuel
Les mauvaises choses en revanche ce sont toutes les autres ! Pourquoi sont-elles mauvaises ? Tout simplement parce qu’elles sont imprévisibles et chronophages :
on ne sait pas en amont combien de temps va nous prendre la résolution de ce bug
on doit tester à la main à chaque fois, si l’on doit passer par 3 ou 4 écrans avant d’arriver à ce que l’on veut tester, ça peut devenir très lent !
on ne sait pas si ce bug va revenir
on ne sait pas si on sera capable de remarquer que le bug est revenu s’il revient
Ce serait tellement plus simple s’ils pouvaient exister un processus automatisé qui prenne en compte les bonnes pratiques citées plus haut, tout en remédiant aux mauvaises que l’on vient d’évoquer…
Les tests automatisés à la rescousse
C’est exactement le rôle des tests automatisés. Ces tests prennent tout simplement la forme d’instructions qui vont venir “tester” notre code, et s’assurer que le résultat est celui attendu. Ces tests peuvent être exécutés à la demande, et ainsi servir de “filet de sécurité” pour s’assurer que notre code fonctionne correctement et qu’un bug ne reviendra jamais !
Prenons un exemple très rapide pour comprendre le principe. Voici un exemple de fonction que l’on est amené à tester. Cette fonction a pour but de calculer le score total d’un quizz en fonction des réponses apportées :
Cette fonction prend en paramètre un tableau de réponses qui se présentent sous la forme : [{ isCorrect: true, score: 2 }, { isCorrect: false, score: 1}, {isCorrect: true, score: 5}]
et qui calcule donc le score global en additionnant le score des bonnes réponses.
Comment nous testerions ce code en “console.log driven development” ? On remplirait sûrement un petit quizz de façon assez aléatoire juste pour voir si le score calculé est le bon. On pourrait donc reprendre l’algorithme de test vu plus haut :
Remplir les réponses au quizz en se rappelant les bonnes réponses qu’on a données = prendre conscience de l’état initial du système
On clic sur le bouton pour calculer le score du quizz = effectuer une action sur ce système
On constate si le score est bien calculé = constater le problème éventuel
À la place de faire nos console.log
à la main, on pourrait déjà commencer par les rendre “dynamiques”, en écrivant ce genre de code par exemple :
Lorsque l’on exécute la fonction testComputeQuizzScore(),
si la console affiche “✅” c’est que notre fonction a le bon comportement ! Sinon la console affichera “❌”.
À chaque fois que l’on veut s’assurer que notre fonction computeQuizzScore()
fonctionne correctement, plutôt que de tester à la main en remplissant le quizz, on pourra donc appeler manuellement la fonction testComputeQuizzScore()
Mais il y a encore un problème, j’ai employé le mot “manuellement” dans le paragraphe précédent. Il n’y a donc toujours rien d’automatisé ici !
Idéalement il nous faudrait donc un outil permettant d’automatiquement exécuter cette fonction dès que le code testé a changé ! Eh bien, c’est exactement la raison d’être des frameworks de tests.
Jest, un framework de tests populaire
Il existe plusieurs frameworks de tests, mais je vais parler ici de celui que je maîtrise le mieux dans l’écosystème JavaScript et qui est par ailleurs très utilisé : Jest
Le principe sera le même pour tous les frameworks : exécuter des tests et donner le feu vert s’ils passent tous avec succès ou le feu rouge si certains sont cassés. Ces tests peuvent être exécutés de façon automatique à chaque fois que le code testé est modifié, assurant ainsi d’être tout de suite prévenu si un code ne fonctionne plus.
Sans rentrer dans les détails, pour ne pas sortir du périmètre de cet article, ces frameworks de tests mette à disposition des fonctions qui permettent d’écrire le genre de fonctions que l’on a écrit plus haut avec testComputeQuizzScore().
L’équivalent avec Jest serait donc :
Le premier bloc, describe()
, permet de donner une indication à destination du développeur sur ce que l’on teste, et le bloc it()
indique clairement ce qui est testé.
Alors que nous faisions manuellement la condition sur le score qui doit être égal à 5 auparavant, ici cette vérification est incluse dans le framework grâce au mot clé expect
qui permet de définir une assertion. L’avantage c’’est que cela peut nous donner un message clair si le test ne donne pas le résultat attendu. Si par exemple je crée volontairement un bug en ajoutant un +1 au score calculé dans la fonction computeQuizzScore()
de sorte que la valeur 6 soit retournée au lieu de 5, alors Jest nous donne un message d’erreur clair :
Grâce à ce test, nous avons maintenant un filet de sécurité qui empêche toute régression sur la fonction computeQuizzScore()
. C’est là l’un des grands intérêts des tests automatisés !
De plus, le feedback est beaucoup plus rapide ! Plus besoin de remplir tout le quizz à chaque fois que l’on veut tester que le bon score est retourné !
Conclusion
Grâce à ce petit exemple, on a pu comprendre l’intérêt d’avoir des tests automatisés pour nous aider à être confiant sur le bon fonctionnement de notre code.
Nous venons ici seulement chatouiller la surface émergée de l’iceberg que représentent les tests automatisés ! Nous verrons dans les prochains articles que ce que nous venons de faire correspond à une méthode appelée “test after” et qu’elle n’est pas la plus adaptée. Nous verrons aussi comment savoir ce que l’on doit tester, et comment écrire des “bons” tests unitaires. J’emploie pour la première fois le mot “unitaire” car je ne voulais pas brouiller le message de cet article avec des notions sémantiques, mais nous verrons que c’est primordial de comprendre la typologie des tests. Et si ici “l’unité” testée est juste une fonction, nous verrons que ce n’est pas la règle si l’on veut avoir une suite de tests cohérente et dans laquelle on peut avoir confiance :)
À la semaine prochaine, et en attendant :
Happy Coding :)