Menu

La première fois que j’ai voulu écrire des tests unitaires pour un de mes programmes, ma première crainte était la masse de travail que le coverage allait représenter. Mon code faisait quelques milliers de lignes, je m’imaginais déjà passer des semaines à écrire tous les tests correspondants. J’ai donc écrit un premier test, pour vérifier que j’avais bien installé l’outil… et j’ai eu 73% de code coverage.

Le paradoxe du coverage à 100%

Ce type de paradoxe est fréquent lorsqu’on débute dans les tests. Créer des tests semble soudain plus rapide. On a bien entendu besoin d’un peu de temps pour réaliser les autres tests, couvrir les derniers pourcents, mais on y arrive. Finalement, le 100% final s’affiche, notre coverage est complet, on peut livrer le produit !

Victoire !

Le produit part en production, les premiers utilisateurs commencent à s’en servir… et les rapports de bugs pleuvent. Mais comment est-ce possible ? Nous avions 100% de coverage, chaque fonction est testée dans ses moindres branchements logiques ! Si cela vous rappelle quelque chose, et surtout si cela vous rappelle quelque chose de récent, l’explication est simple : vous ne testez pas la bonne chose.

Mais alors, que faut-il tester ?

Reprenons depuis le début. Pourquoi écrivez-vous des tests ? Après tout, c’est long, c’est pénible et soyons honnêtes, on a mieux à faire de nos journées. Alors, quel intérêt d’en écrire ? Trop souvent, la réponse sera de l’ordre de “c’est dans mon contrat” ou pire, “c’est nécessaire pour faire du code propre”. Dans ce cas, je dirais sans hésiter que pas de tests du tout sont mieux que des tests mal faits.

Une réponse partielle mais plus correcte serait “pour éviter les régressions”. Le code est (normalement) quelque chose de vivant : on ajoute des éléments, on en enlève, et chaque modification peut introduire un bug dans une fonctionnalité faite quelques itérations plus tôt. Des tests complets permettent de détecter une telle situation dès son apparition et donc de la corriger avant la production.

Mais cette réponse est incomplète, car elle se focalise sur le code et son contenu. Au risque de répéter ce que je disais dans mon article précédent, notre travail en tant que développeurs n’est pas d’écrire du code, c’est de résoudre des problèmes. Nos utilisateurs ont un problème à résoudre et nous proposons un moyen de répondre à ces besoins.

Notre code n’a pas d’autre usage que de fournir un service à l’utilisateur.

Écrire des tests, cela ne doit donc pas avoir pour but de vérifier que notre code est bien écrit, mais qu’il rend le service qu’il doit rendre. Sans ça, on tourne en rond en vérifiant que du code est bien un code correspondant à un bout de code, en perdant totalement de vue l’utilisateur final.

Et on tourne en rond encore et encore...

On teste un besoin, pas du code.

Donc, on écrit des tests pour vérifier que notre code réponde bien au besoin de nos utilisateurs. Cela peut paraitre un sophisme, mais c’est en fait un changement fondamental de notre mode de pensée. Si on teste que notre code réponde bien à un besoin utilisateur, alors la non-régression devient une évidence : on ne teste pas que notre code fonctionne toujours, on vérifie que même avec nos dernières modifications il continue de répondre au besoin exprimé. L’évolution des tests va de pair avec l’évolution du besoin utilisateur : si le besoin change, les tests changent. Si les tests changent, le code leur correspondant va probablement devoir changer également.

C’est cet état d’esprit, centré sur l’utilisateur et le besoin client, qui est à la base du Behaviour Driven Development. En mettant le besoin au centre du processus de tests, le test devient un outil de dialogue entre le PO et la dev team. En permettant une meilleure compréhension partagée de la story, il facilite les échanges et améliore le produit. C’est dans ce sens et uniquement dans ce sens qu’ils fournissent une sécurité au code. Je reviendrais sur le BDD et les outils pour l’implémenter dans un prochain article.

À l’inverse, des tests écrits parce qu’il faut faire des tests sont non seulement inutiles, mais peuvent nuire à la qualité du code en général. En effet, on va alors modifier notre code pour le rendre plus facilement testable, voir penser que nos tests remplacent le besoin client : s’il y a un problème, ce n’est plus que le code bug, c’est que le client l’utilise mal.

You're doing it all wrong

Code coverage et need coverage

La question du code coverage prend un sens tout particulier lorsqu’on l’aborde sous l’angle du besoin client. En effet, on peut ajouter un nouvel indicateur imaginaire : le need coverage. À quel point est-ce que nos tests couvrent bien le besoin client réel ? Avons-nous bien pris en compte tous les cas possibles en écrivant nos tests ?

Si oui, alors en toute logique nous devrions avoir un code coverage de 100%. En effet, notre code n’existe que pour répondre à un besoin client. Si la totalité du besoin client est couverte par nos tests, mais que notre code lui ne l’est pas, cela signifie qu’une partie du code sert à réaliser quelque chose qui ne correspond pas à un besoin client. En d’autres termes, c’est du code inutile, et il n’a pas à être présent dans notre application.

À l’inverse, avoir 100% de code coverage veut seulement dire qu’on a testé tout notre code. Rien ne nous dit qu’on a testé tout le besoin client. Les bugs qui apparaissent viennent systématiquement de cet écart. Un bug, c’est une chose qu’un client peut faire, mais que nous n’avions pas prévue. À nous d’atteindre les 100% de need coverage pour les éliminer.

Et vous ? Comment faites-vous vos tests ?

Published in Programmation

Yann Piquet

Passionné par le développement depuis l'enfance, j'aime particulièrement le développement web et les approches full stack qui me permettent de voir toute la chaîne allant du serveur à l'application React.

Leave a comment

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