Pour d'autres, les tests unitaires sont une perte de temps, une façon débile de doubler le volume de code à maintenir. Doubler, voire plus...
Lorsque j'ai lu eXtreme Programming de Kent Beck, j'étais emballé par la façon de penser de cette méthode, mais le seul point sur lequel je n'émettais aucune réserve était celui sur les tests unitaires.
Je n'écris pas cet article pour faire l'apologie du test unitaire. Les tests unitaires font maintenant partie de ma "boîte à outil du développeur" et j'y ai recours quand je l'estime nécessaire, mais point trop n'en faut : ils ne sont pas toujours indispensables. Cet article raconte plutôt comment j'ai découvert et utilisé les tests unitaires dans mes divers projets.
Le Beck dans l'eau
Chez moi, sur un petit projet de mon cru, j'ai donc décidé d'essayer, à l'aide de CppUnit. J'avais très rapidement écrit la veille une classe de pointeurs automatiques dont j'avais besoin pour mon projet ; j'avais fait le service minimum pour que le code fonctionne pour mes besoins du moment. J'ai donc repris cette classe et écrit les quelques tests nécessaires pour valider ce que j'avais fait. J'ai compilé, puis lancé les tests. Boum! Deux bugs d'un coup ; j'étais convaincu.
Après les avoir corrigés, j'avais la preuve que mon pointeur automatique fonctionnait bien. Ce n'était plus le trop-plein de confiance du programmeur éternellement arrogant qui se dit "cette fois c'est sûr, c'était le dernier bug". J'avais balayé tous les cas possibles d'utilisation de ma classe, et j'avais la preuve que ça fonctionnait. Je n'avais jamais eu ce niveau de confiance dans mon code auparavant. J'ai donc décidé à ce moment-là que les tests unitaires étaient une bonne chose.
En retournant au travail le lendemain, j'ai tenté d'expliquer à mon collègue développeur pourquoi je pensais que les tests unitaires étaient une très bonne chose. J'étais tellement emballé par cette pratique que sa réponse me décontenança un peu : "ça à l'air pénible, il faut modifier les tests à chaque fois qu'on modifie l'objet testé".
Certains sont parfois atrophiés de la vision à moyen et long terme.
Pas assez de tests, Petit Scarabée
Plus tard, j'avais à écrire un outil d'exécution de tests automatiques. L'outil était capable d'exécuter des scripts en VBScript en utilisant un composant COM de chez Microsoft, mais afin de le rendre plus intelligent, il fallait que l'outil comprenne par lui-même, au moins dans une certaine mesure, la syntaxe du VBScript. Il y fallait donc écrire un petit parser. En soi, c'est déjà un cas où l'utilisation des tests unitaires se révèle très bénéfique.Au final, cette façon de faire m'a pris plus de temps que si j'avais fait le travail moi-même, mais cela permit à ma stagiaire de progresser de manière remarquable. C'était aussi une belle illustration d'un des principes invoqués par les évangélistes du test unitaire : une fois que le test passe, on a fini. La consigne de ma stagiaire était donc : "fais ce qu'il faut pour que les tests passent, puis passe à la suite." La tentation est grande, quand on est jeune développeur, de passer beaucoup de temps à polir et à peaufiner son code, souvent en pure perte. Là, pas de problème, le test servait d'indicateur pour dire que le travail était fait. Et moi je savais qu'il était bien fait.
Bien sûr, je me suis fait avoir. Pour l'une des fonctions, j'avais écrit un test un peu trop simpliste, et la fonction qui en résulta fut très simpliste aussi. Mais le plus important était qu'elle faisait ce qu'on lui demandait. Les tests unitaires ne sont pas un moyen miraculeux pour écrire du code sans faute. Je vois l'avantage comme ceci : la probabilité d'avoir un bug non découvert lorsqu'on met en place des tests unitaires diminue fortement dans la mesure où il doit se trouver dans le code testé et dans le test.
C#, précis, pointu
Aujourd'hui, je travaille à nouveau sur une sorte de parser. J'adooooore les parsers. Le "programme" est écrit sous forme de tableau Excel et mon outil, écrit en C#, lit ce tableau Excel et génère en résultat une séquence correspondante dans un autre environnemment appelé TestStand.Comme je ne connaissais pas C# et que je n'avais jamais piloté Excel ni TestStand depuis un environnemment .Net, je décidai rapidement de me reposer largement sur les tests unitaires (avec NUnit), qui me permettraient à la fois de valider mes suppositions sur le fonctionnement de ces trois APIs et de valider mes algorithmes d'analyse lexicale.
Et c'est donc comme ça que je travaille aujourd'hui. Dès que je veux faire quelque chose que je n'ai pas encore fait avec Excel piloté par C#, j'écris un test unitaire, qui va lire dans l'un de mes fichiers de tests, pour valider ma compréhension sur le sujet. Une fois que mes tests passent, je sais que je peux l'utiliser en confiance dans mon programme.
C'est un autre point important des tests unitaires que cet exemple illustre : les tests unitaires peuvent permettre de valider un composant logiciel exterieur. Rien de tel qu'une batterie de tests pour savoir comment fonctionne un composant exterieur.
Et ce n'est pas fini ! Récemment, j'ai vu surgir un bug étrange, qui semblait aléatoire. Avec des points d'arrêts bien placés, j'ai fini par comprendre quelle séquence d'appels faisaient apparaître ce bug. J'ai immédiatement mis en place un test unitaire pour reproduire, dans une sorte d'environnement de laboratoire, cette même séquence d'appels. Effectivement, en exécutant ce test, le bug se reproduisait. J'avais donc un moyen simple de réexécuter cette séquence pathogène à volonté, jusqu'à ce que je parvienne à isoler la cause du bug. Et ça, c'est vraiment confortable. Le test est bien sûr toujours là, et s'assure que le bug ne resurgisse pas (sous cette forme, du moins).
Not an addict
Si vous jettez un œil aux sources de Poseidon, vous verrez qu'il y a en tout et pour tout... 1 test unitaire.Poseidon est un synthétiseur virtuel, et de ce fait, il s'agit d'une boîte dans laquelle il y a des objets qui synthétisent ou traitent du son et qui se passent ce son entre eux pour créer finalement le son final. Les algorithmes sont essentiellement des algorithmes de traitement du son, et décider si un tel algorithme est bien implémenté relève plus souvent du subjectif que de l'objectif.
Je n'ai donc pas écrit de batterie de tests unitaires pour Poseidon. En fait, j'écris essentiellement des tests pour les algorithmes logiques et calculatoires, et pour les structures de données, car ce sont ceux qui apportent le plus de plus-value.
Ceux qui disent que les tests unitaires ne servent à rien, je les invite à essayer : pour certains types de code, c'est l'une des méthodes les plus efficaces pour chasser le bug. Je ne crois pas cependant que ce soit la panacée, comme les gens du Test-Driven Development voudraient nous faire croire. Finalement, les tests unitaires, c'est comme les pommes : "An apple a day keeps the doctor away", mais, gros mangeurs : gare au sucre !