TDD contre les montagnes russes
A l’époque où je ne connaissais pas encore la démarche Test Driven Development, mon travail connaissait des hauts et des bas:_
lundi 11h : questions au client, fait quelques diagrammes, prêt à coder le module xyz mardi 18h : programmation et enrichissement de la conception mercredi 16h : plus compliqué que prévu, mais je tiendrai le délai de vendredi mercredi 19h : stop; je dois revoir encore la conception jeudi 12h: décidé de réécrire from scratch; bien plus fluide, code plus propre ! vendredi 10h : ce midi je passe mes tests! (au fait, ce débogueur est nul). vendredi 16h : ça y est, je passe mes tests vendredi 19h : panique. Rien ne marche. Raté la bière du vendredi. vendredi 21h : déjà 21h mais ça devrait être OK pour la recette.
[Journal de bord - ca 1998]_
Mon approche manquait de discipline. Je revoyais en profondeur mon design, sans savoir si le programme marchait ou non. Je cherchais mes erreurs avec un débogueur — cela prenait des heures. Croyant mon programme presque fini, je passais mes tests et découvrais que c’était loin d’être le cas. Mon moral aussi jouait les montagnes russes, passant de l’euphorie (“je suis génial!”) à la rage impuissante (coup de poing sur la table). Pas de quoi inspirer confiance à mes coéquipiers ou mon chef de projet.  Le changement qu’a apporté TDD ? Ce n’est pas de supprimer ces variations dans ma productivité, mais seulement d’en réduire l’amplitude. TDD est une pratique de régulation de la programmation.
Pourquoi ça marche
Pour améliorer les performances d’un processus, il faut d’abord le stabiliser. En instaurant des étapes très courtes de test, de code, de correction d’erreur et de redesign, TDD limite rigoureusement les dérives de programmation. Programmer, c’est gérer un flot continu et irrégulier de décisions, à différents niveaux. Ces décisions s’appuient sur un échafaudage d’hypothèses entrelacées qui constituent pour ainsi dire la connaissance qu’a le développeur du fonctionnement de son code.
“… et quand l’exécution repasse dans cette boucle la deuxième fois, l’indicateur a basculé […] donc ce bloc est le nouveau bloc le plus récent […] d’ailleurs ce n’est pas le rôle de cette classe… [etc.]”
Cet échafaudage représente un défi pour l’intelligence, la patience, et la sagacité du développeur. Son code est donc sujet aux erreurs. Lorsque ces erreurs sont détectées, l’échafaudage s’écroule. Plus le test arrive tard, plus la localisation et la correction coûtent cher.
“… Le résultat est faux parce que les montants sont cumulés avec ceux de l’année dernière parce que le tableau n’était pas initialisé en début de traitement, parce que ce tableau est passé à la procédure, parce que c’est une variable que j’ai déplacée et rendue globale parce que je me souciais des performances. Je me demande si j’ai fait d’autres erreurs de ce genre ?”
Le problème n’est pas tant de faire des erreurs — elles sont naturelles, inévitables. Le problème est de détecter ces erreurs trop tard. TDD permet de construire des échafaudages plus petits, moins périlleux, et de consolider cette construction de manière continue. Voici comment:
La démarche TDD consiste à
- écrire un test (et s’assurer qu’il ne passe pas déjà) 2) écrire le code le plus simple qui fasse passer ce test 3) refactorer Puis retour au step 1)
Chaque nouveau test formule une nouvelle hypothèse. Les tests précédents vérifient que mes autres hypothèses précédentes tiennent toujours. Lorsque mon code casse plusieurs tests alors que je ne m’y attendais pas, cette alerte me rappelle à la discipline. Je défais mon code avec undo et je reviens à une situation stable.
Les tests vérifient très peu de code, et ils cassent à la première erreur; aussi le temps requis pour localiser cette erreur dépasse rarement la minute. Les tests couvrant mon code de manière quasi irréversible, mes efforts passés sont donc protégés comme par un effet de cliquet.
Un test difficile à écrire ou à faire passer révèle bien souvent un problème de modularité. Je peux alors refactorer le code afin de le rendre plus modulaire, et ainsi faciliter la création des nouveaux tests. Réciproquement, les tests soutiennent mes actions de refactoring en démontrant que celles-ci ne changent pas le comportement du programme. Le refactoring soutient les tests, et les tests soutiennent le refactoring.
Pas de recette miracle
TDD n’est ni infaillible, ni automatique. Il est toujours possible d’écrire un test simple, et néanmoins difficile à faire passer; ou bien d’écrire plus de code, ou du code plus complexe, qu’il n’est requis pour faire passer le test. Pour éviter de tels errements, quelques conseils :
Respecter le cycle de base 1) écrire un test qui ne passe pas, 2) puis le faire passer, puis 3) refactorer, et recommencer.
Toujours écrire le code le plus simple qui fasse passer le test.
Noter son plan et ses idées sur une todo list afin de garder l’esprit clair.
Se demander: quel nouveau test permettrait d’avancer d’un pas vers l’objectif ?
Lors du refactoring, se demander à propos du code : Exprime-t’il l’intention ? Contient-il des redondances ? Est-il équilibré (taille des classes/méthodes) ?
Se demander: quel refactoring permettrait de simplifier l’écriture du prochains test ?
Ces conseils, suivis scrupuleusement, aideront-ils à résoudre tous les problèmes de développement ? Non, bien sûr. Produire du code maintenable et fiable est une chose. Intégrer sans erreur ce code dans un système plus complexe, choisir une architecture; inventer un nouvel algorithme en sont d’autres.
Conclusion
TDD est une discipline de programmation régulée qui facilite la conception émergeante du code, tout en réduisant le temps de mise au point. Si vous constatez sur votre projet les symptômes suivants :
Incidents en recette liés à des erreurs de programmation
Code volumineux, redondant, peu factorisé
Temps de mise au point incontrôlable
Nouveaux défauts insérés lors de la correction de défauts
Conjectures (“si ça se trouve…”) à propos du comportement du code
Impossibilité d’écrire des tests unitaires sur le code
Réactions de colère, de peur, de tristesse face au code
Alors vous devriez peut-être envisager TDD.
PS: Le livre de référence sur TDD: TDD by Example, Kent Beck