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 à

    1) é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

4 commentaires pour “TDD contre les montagnes russes”

  1. Bel article Christophe, mais la conclusion n’est-elle pas un peu un euphémisme? La pratique de TDD, surtout après de telles arguments, n’est pas peut être à envisager, mais à absolument adopter.

  2. Bonjour,

    je découvre (très en retard) cette méthode de développement que j’aimerais vraiment approfondir. Auriez-vous des références d’ouvrages à proposer?

    J’ai entendu parler de « Test Driven Development: By Example » de Kent Beck, il est en anglais et basé sur xUnit, j’ai aussi trouvé « JUnit, Mise en oeuvre pour automatiser les tests en Java » de Benoit Gantaume, qui est plus spécifique à JUnit qu’au concept TDD mais en français et tout aussi intéressant.

  3. Bonjour PsK,

    Le meilleur moyen d’apprendre le TDD est de le pratiquer, et c’est encore mieux de se faire aider par quelqu’un. Beaucoup de praticiens s’exercent avec des Katas de code, et en plus les mettent à disposition sur vimeo and co. Une recherche google donne pas mal de réponses. Dans certaines villes également, les développeurs se rencontrent lors de coding dojo pour pratiquer et échanger, et il n’y a pas meilleur endroit pour apprendre et rencontrer d’autres passionnés.
    En lecture, le livre Refactoring est une référence, étant donné que c’est une pratique indissociable du TDD.

    J’espère vous avoir aidé un peu.

  4. Merci pour ces infos,
    je n’avais jamais vraiment pensé à ce genre d’entraînement, mais c’est vrai qu’il est plus simple de se faire aider, d’échanger et de s’exercer en communauté…

    Je vais suivre ces pistes mais me garde quand même les référence des bouquins sous le coude histoire de faire le tour de ces méthodes avant de me lancer dans l’apprentissage à proprement parler.

Laissez un commentaire