Retour à la liste des articles

Pourquoi le TDD est-il si indispensable pour vos projets ?

21 avril 2020

La conception et le développement logiciel est un monde créatif et non simplement productif.
Il y a plusieurs façons d'arriver à ses fins et certaines sont bien plus astucieuses, intelligentes, élégantes et avantageuses que d'autres.
C'est pour cela que chez WealCome, nous considérons la programmation comme un véritable art.

Dans cet article, nous allons mettre en valeur une façon de coder ultra-puissante bien que mal cernée qui apporte tout un lot d'avantages incroyables pour vos projets que vous n'imaginiez probablement même pas.

Cette façon de coder, cette discipline, s'appelle le Test-Driven Development, alias TDD.

Abordons le sujet avec une prise de conscience du problème majeur de la programmation qui est sans aucun doute l'ennemi numéro 1 de vos délais, de vos budgets et donc de votre bonne humeur : la complexité accidentelle.

La complexité accidentelle

Qu'est-ce que la complexité accidentelle ?

Vous emménagez dans votre nouvelle maison, et votre enfant de 8 ans dispose alors de sa propre chambre.
Pour lui faire plaisir, vous avez tout rangé pour lui :
ses pulls bien pliés et ordonnés par fréquence d'utilisation dans l'armoire, ses chaussures toutes alignées sur le sol, ses affaires scolaires bien disposées sur le bureau etc.

Le premier jour, votre enfant cherche son pull préféré : facile à trouver, il est sur le haut de la pile; action rapide, il en sourit !

Avez-vous déjà aperçu la chambre d'un enfant non éduqué/sensibilisé au rangement et sans assistance de ses parents ?
Au bout de 2 semaines seulement les pulls nagent au milieu des chemises; les 3/4 de ces dernières se retrouvent au sol, ses chaussures sont sous le lit, ses chaussettes sur la chaise de bureau et sa trousse malencontreusement tombée et cachée au fond de la poubelle.

Vous vous doutez bien que rechercher un objet dans un tel désordre sera bien plus COMPLIQUÉ, fastidieux et long que lors du premier jour !

Le code source décrit la même caractéristique : un code mal entretenu et qui ne s'adapte pas aux nouvelles exigences de l'application "rouillera" et entraînera de lourdes conséquences impactant fortement la productivité.
Au début tout à l'air niquel, mais attendez deux semaines, si vous êtes laxiste, et vous verrez ...
Dans un tel contexte, une tâche devant coûter X dans un contexte de code soigné, coûte X + Y (Y pouvant être gigantesque) avec un code mal dompté.

"Accidentelle" car ce type de complexité surgit suite à un cumul de négligeance.

Un stress permanent destructeur dû à une pénibilité certaine

Le plus malheureux dans tout cela, c'est que beaucoup de développeurs ne peuvent jauger l'impact d'une complexité accidentelle en amont ; ils la subissent en live, en phase de coding ; sur leur propre code !

Le parent à son enfant :
"En combien de temps vas-tu trouver ta chaussette rouge à pois ?"
"Euh ... ça dépend de ma ..... chance !"
"Vous vous doutez bien que cette réponse est légère et incongrue".

Cela ne vous rappelle-t-il pas ce fameux dialogue tellement présent en entreprise ?
"Combien de temps pour la fonctionnalité permettant à un utilisateur de commander plus d'un produit à la fois ?"
Réponse donnée : "10 jours environ Monsieur !"
La réponse réelle dans l'esprit du développeur : "Euh ... ça dépend de ma ..... chance !"

La réponse donnée qui s'apparente malheureusement à un engagement pour le Manager génère un stress énorme chez le développeur car celui-ci nage en pleine incertitude avec un sentiment d'impuissance.

Conséquences immédiates :

  • Le développeur ressent constamment un manque cruel de temps
    Il fait l'impasse sur toutes bonnes pratiques de code dont il est conscient.
    Le code rouille rapidement et il sait lui-même qu'il livrera un travail bâclé.
    Il croise les doigts tous les jours pour que les failles ne soient pas perçues par le client.
  • Le code produit est peu intelligent et ne permet aucune flexibilité
    Design de code non testable, notions hard-codées (codées en dur), capacité de changements et d'évolutions très faible.
  • L'agilité en pâtit et devient plus possible trop rapidement
    L'agilité, prônant l'adaptation constante en réaction aux feedbacks clients est sacrifiée, impactant l'humeur des Managers et surtout des clients.
  • Un burnout à l'horizon dû à une surdose de travail et de recherche pénible
    Cf la recherche d'objet de l'exemple précédent, pour tenter de rester fidèle aux délais annoncés.
  • Délais et coûts explosés
    Menant alors à une perte de confiance complète du client et des Managers.
  • Atmosphère générale pesante dans l'équipe
    Ôtant le sourire de chaque visage et la confiance en autrui.

Vous aurez donc compris que la complexité accidentelle est l'ennemi numéro 1 de l'estime de soi, de sa dignité et du professionalisme.

Prends soin de ton code fréquemment pour t'en sortir !
Oui mais ...

À première vue, prendre soin de son code très régulièrement semble une faculté à la portée de tout développeur à bon CV, et donc un traitement simple et efficace contre cette complexité accidentelle croissante.

Ne vous fiez pas aux apparences, prendre soin du code source est un réel challenge pour bon nombre de développeurs qui n'ont jamais creusé le sujet.
En effet, il faut prendre en compte quelques facteurs dérangeants, imposants et imprévisibles.

Prenons l'exemple de l'enfant qui cherche fréquemment des objets dans sa chambre pour mettre l'accent sur les points clé de la difficulté :

  • Manque de compétences
    L'enfant trouve 3 pulls au sol, il souhaite les ranger dans l'armoire dénotant alors une bonne volonté de sa part d'aller de l'avant !
    Il les plie à sa manière (farfelue) et tente de les faire rentrer.
    Il les rentre, mais ... cela provoque la tombée de 4 autres pulls.
    Il retente, pour finalement trouver un compromis avec 3 pulls en boule ; compromis dérangeant pour les prochains rangements.
    Vouloir apporter un soin, et savoir apporter le bon soin sont deux univers bien distincts.
    L'un est simpliste basé sur la bonne foi et l'envie ; l'autre relève de connaissances théoriques, d'expériences pratiques, d'intelligence et de doigté.

  • Travail collaboratif
    Le parent vient voir son enfant :
    "Stp, tu peux ranger la chambre de ton frère ?"
    "Euh... il va m'en vouloir si je range à ma sauce et qu'il ne retrouve plus ses affaires !"
    La conception et le développement logiciel consistent en un travail collaboratif en entreprise.
    Il faut donc que le code source "transpire" les intentions des développeurs explicitement et clairement, expose des solutions cohérentes et maintenables, et dans l'idéal une approche communément discernable, dont l'efficacité est réputée et prouvée (par exemple les Design patterns du GOF, paires nommées de problèmes/solutions).
    Harmonie efficace très difficile à trouver sans changements drastiques des habitudes.

  • Syndrome de l'effet de Dunning–Kruger causant la déformation inconsciente des bonnes pratiques
    Le soin est une notion subjective basée sur des fondements totalement objectifs, vérifiables et "lucratifs".
    On observe très (pour ne pas dire trop) fréquemment le cas où un développeur (Tech Lead compris) n'a pas les connaissances théoriques requises pour comprendre le fondement des concepts et leurs subtilités.
    Ainsi, les bonnes pratiques de code sont souvent grossièrement déformées involontairement.
    Un tel "soin" basé sur aucune base de réflexion suffisamment sérieuse peut être désastreux et causer davantage de sinistres dans le code source.

  • Hétérogénéité des niveaux dans une équipe
    Le parent à son enfant :
    "Je t'amène ta soeur, elle va t'aider à ranger !"
    "Mais Maman, elle range très mal, elle n'arrive pas à suivre ma façon de faire !"
    L'enfant doit être conscient que s'il désire une chambre bien rangée à tout moment, il faudra avoir le courage de reprendre le travail de sa soeur en appliquant une énième couche de soin sur une couche déjà existante mais bancale.
    Survient alors un tout autre problème, la peur de toucher au code d'autrui par crainte de faire diminuer la qualité du soin potentiellement déjà appliqué !
    L'enfant ne voudra donc pas que sa soeur impacte son propre chantier de peur de ne pas pouvoir rétablir l'ordre.
    Bonjour l'humeur accablante et méfiante au travail, tellement fréquente en entreprise !

  • Force de caractère peu commune nécessaire.
    Le cran de savoir dire "non" à tout ce qui pourrait entraver la qualité de code; même aux décisions managériales n'est pas un trait commun parmi les développeurs.
    Pour détailler cet aspect, nous pensons notamment à cet article : Saying No ainsi qu'à l'analogie en vidéo suivante très pertinente.
    Celle-ci évoque une métaphore de la complexité accidentelle avec des bédouins du désert n'ayant pas eu le cran
    de capturer le voleur de poulets à temps ; les complications s'amplifiant par conséquent en mode "domino"
    l'une après l'autre jusqu'à l'échec irreversible :


Ces seuls points-là vous font alors comprendre que surmonter la complexité accidentelle est un challenge des plus difficiles paraissant insurmontable; dépendant de plusieurs facteurs psychologiques, pratiques et techniques à la fois.

Comment faire alors ?
Doit-on accepter cette soi-disant fatalité et faire comme si les acteurs du projet ne souffraient pas ?

Le TDD, le vaccin le plus efficace contre la complexité accidentelle

Nous espérons qu'il est assez clair pour vous que la complexité accidentelle est causée principalement par un code source délaissé, rouillé, qui ne peut se voir amélioré continuellement et progressivement sans risque de casser le système ; ainsi que par une absence absolue d'auto-feedbacks permanents et intelligents concernant la pertinence du code et sa qualité.

La tentative d'application de soin dans un environnement si bancal et non maîtrisé est sujette à de fortes potentielles régressions synonymes donc de très probables licenciements.

Personne ne veut prendre de si gros risques n'est-ce pas ?

Plus qu'un code rouillé, on parle d'un code "scellé" malgré lui, par le sentiment d'impuissance des développeurs.
Ces derniers sont alors pris en otage par leur propre code éternellement dérangeant.

Il faut donc un vaccin à cette fatalité apparente ; une discipline de fer ; une discipline quasi militaire qui permet de ne pas tomber dans ces travers; qui permet de changer fièrement ses habitudes jusque-là néfastes.

Ce vaccin existe et porte le nom de "Test-Driven Development", alias "TDD".
Il exige une remise en question totale des ses habitudes.

Le monde déteste le changement, c’est pourtant la seule chose qui lui a permis de progresser.
(Charles F. Kettering)



TDD le fameux vaccin pour avoir un code soigné ?! Mais c'est juste une technique de tests non, ça ne change pas la face du monde !

Ahhhhhh nous y voilààààà ...

Dire que TDD consiste à rédiger des tests unitaires, c'est passer totalement à côté de son concept et ignorer son réel objectif ainsi que l'art de sa pratique.

Et pourtant, combien de boîtes pensent que TDD est une technique de testing, au bas mot 99% ?
Et pourtant, Combien de boîtes de nos jours prétendent pratiquer TDD ? Allez, 100%, rien qu'à lire les offres d'emplois actuelles.

Où est la faille de compréhension ?
Où est la petite bête qui génère autant de quiproquo autour de TDD ?

C'est très simple et ça tient sur deux lettres.
Ils voient "T" alors qu'il faut voir "TD" (dans TDD) !

Le mot "Test", isolé du reste, a tendance à laisser penser que TDD est une méthode de testing dont la seule directive (disent-ils) est d'écrire un test avant de coder, POINT.

Un développeur peut très bien écrire un test avant de coder, tout en laissant libre cours à son imagination et aux diverses anticipations dans le code source, ceci matchant avec la définition incomplète précédente.
S'occuper de tous les axes d'un algorithme à la fois au sein d'une même "session de code" (c-est-à-dire avant de faire passer le test en question) est le meilleur moyen de perdre l'équilibre et de commettre des erreurs voire de s'égarer totalement.

La pratique est alors si mal comprise qu'elle est plus destructrice qu'amélioratrice.

Prenez maintenant la portion "Test-Driven", on y lit "Driven" ...

Un TDD est bien pratiqué lorsque la rédaction du code se laisse totalement portée par l'exigence du test en cours.
On se sent D-R-I-V-E-N (autrement dit PORTÉ / TRANSPORTÉ / GUIDÉ / EPAULÉ / REDIRIGÉ / STOPPÉ / AVANCÉ).

Le pouvoir de l'imagination et d'anticipation pouvant violer le fameux principe YAGNI est dangereux comme nous allons le voir plus bas.

Michaël AZERHAD a d'ailleurs réalisé un podcast "On fait du TDD chez nous", on vous ment, attention ! dénonçant les mauvaises interprétations annihilant defacto tout bienfait de la pratique.

Le pouvoir d'imagination doit se voir absorber totalement par le pouvoir du minimalisme requis à l'instant T sans anticipation aucune.
Celui qui permet réellement d'avoir la sensation de se laisser guider par le pouvoir de "l'absurde" (notion fondamentale de TDD), de la simplicité déconcertante de baby-steps finement façonnées, de la naïveté désirée avec comme seul mot d'ordre celui de remplir le strict minimum de code répondant à l'exigence du test actuellement ciblé; rien de plus rien de moins.
L'algorithme émergera alors par à-coups, à la manière d'un jeu d'énigmes continuel, cumulatif et plaisant, donnant tout un sens au mot "D-R-I-V-E-N".

Le terme "D-R-I-V-E-N" implique naturellement l'existence de lois bien strictes qui assistent fortement au respect du laisser-aller par à-coups, de la validation immédiate et fiable du minimalisme choisi répondant au test en cours, ainsi que la capacité d'amélioration continuelle du code source par application de couches de soin (pratique nommée "Refactoring" dans le jargon du développeur) sans crainte aucune.

3 lois principales :

  1. Tu n'as pas le droit d'écrire du nouveau code en production sauf pour faire passer un test (le plus souvent unitaire) qui échoue.
  2. Tu n'as pas le droit d'écrire plus d'un test que ce qui est suffisant pour échouer et l'erreur de compilation est considérée comme un échec.
  3. Tu n'as pas le droit d'écrire plus de code de production qu'il n'en faut pour réussir le test ayant échoué.

Ainsi que d'autres lois additionnelles d'un avantage indéniable nommées la "Transformation Priority Premise"; qui par elles seules démontrent le degré de rigueur exigé par TDD.

Simplifier un problème en se concentrant sur un axe à la fois ; cumulant ainsi axe par axe est l'esprit de TDD.
C'est véritablement une approche par cumul d'étapes de faibles envergures (baby-steps) et franchies avec succès qui au final donne naissance à l'algorithme final désiré.
On parle ainsi d'approche incrémentale de développement.

TDD est donc une discipline stricte qui promet (si bien maîtrisée évidemment) d'éviter toute complexité accidentelle par le simple fait qu'elle permet par sa nature un focus complet et fréquent sur la qualité du code lors des phases de refactoring.
Ces dernières recommandées par la discipline à chaque fois qu'un test passe de l'échec au succès.
On a alors le fameux cycle opératoire propre au TDD :
"RED - GREEN - REFACTOR - RED - GREEN - REFACTOR - ...".

D'ailleurs, si vous creusez bien le sujet, vous comprendrez qu'un test unitaire ne sert pas à tester/valider un code mais de faire en sorte qu'on puisse le refactorer avec aisance ...
Uncle Bob souligne cet objectif essentiel du test unitaire dans sa vidéo de 3 minutes que nous vous recommandons :

La conséquence heureuse et gratuite de cette pratique est qu'elle porte aussi la casquette de testing ; un bonus non négligeable pour assurer les couches de soin futures et éviter toutes régressions et fautes d'inattention stupides mais tellement coûteuses !

Wow ... je comprends mieux le quiproquo, tu peux en dire plus sur les améliorations que provoque un bon TDD sur le projet ?

  • Plus jamais de codes morts ni de codes inutiles
    En effet, le feedback permanent et la volonté de rechercher le minimalisme passant permet de se défaire de tout code mort et inutile.
    On passe alors d'un projet à 60K lignes à un projet à 20K lignes ; gain énorme en lisibilité et maintenabilité.
  • Plus de fautes d'inattention décelées trop tard dans le code source
    Un test exécuté est un feedback rapide en soi sur le code source, vous l'aurez compris.
    TDD prônant une évolution par baby-steps (développement incrémental), le feedback régulier et imminent nous avertit de toute faute d'inattention à chaud.
  • Plus ou quasi plus de mode debug
    Vu que les fautes d'inattention sont décelées à chaud, alors on n'a de moins en moins recours au mode debug pour comprendre des incohérences impensables.
  • Productivité décuplée !
    Plus de mode debug, plus de fautes d'inattention passées sous silence, plus de code inutile/mort; c'est une productivité décuplée !
    Moins de maintenance corrective pour davantage de production de valeurs.
  • Un code testable par défaut
    Savez-vous qu'il est très aisé d'obtenir un code non testable ?
    Dépendances implicites, couplages forts à des composants externes capricieux font qu'un code ne peut être testable.
    Les moins perspicaces d'entre vous diront que les tests unitaires écrits avant ou après le code, c'est "kif-kif".
    Michaël AZERHAD vous propose son petit jeu à 1:41:00 de sa conférence sur le sujet; vous y verrez qu'il est très difficile voire impossible de justifier pleinement et avec pertinence un code existant avec des tests unitaires.
  • Sérenité profonde permettant de tenter de nouvelles syntaxes de code ainsi que concepts d'architecture sans crainte
    Si t'as des feedbacks imminents et rapides, tu peux alors tenter de nouvelles syntaxes de ton langage préféré.
    Changer une boucle "for" classique en une approche plus fonctionnelle avec "map" et une lambda en Java 8 par exemple; sans craindre de "casser" son code.
  • Plaisir de coder incomensurable par le confort généré !
    Ah TDD, ce fameux jeu d'énigmes qui consiste à faire évoluer un code par à-coups jusqu'à matcher avec le comportement algorithmique désiré.
    Une satisfaction de se sentir encadré par les tests lors de la confection de la logique nécessaire.
    Une impression, pour les nouveaux fans de la pratique, d'avoir "revisité" totalement l'art qu'est la programmation !
  • Documentation gratuite
    Le fait de te forcer à façonner un test après l'autre décrivant des baby-steps
    te mène à avoir naturellement une suite complète décrivant complètement le comportement/dénouement de ton algorithme. Celle-ci a bien plus de valeurs qu'un document word non exaustif et très vite obsolète voire déconnecté de la réalité du terrain.
  • Encadrement complet dans le cadre de changements et de couches de soin
    Refactorer son code devient un plaisir inouï !
    Séances de soins progressifs, l'une par dessus l'autre, par le développeur A ou B peu importe, espacées ou non dans le temps, afin de s'adapter sans souffrance aucune aux besoins changeants du client.
    Ça vous fait penser à quelque chose ?
    Oui l'agilité, ou autrement dit l'amélioration continue de l'application et de son code source !
  • Une évolutivité sans contrainte ni souffrance
    Les évolutions apparaissent de manière bien plus régulière et les estimations deviennent bien plus précises (même si toutefois incertaines).
    Le client se voit livrer de la valeur à une vitesse décuplée.
  • Délégations aux collaborateurs de tâches majeures sans anxiété
    Les juniors se verront affecter de meilleures tâches car les plus experts de l'équipe n'auront pas peur de les positionner sur des tâches plus critiques qu'à l'accoutumée car encadrées par les tests.
    Le junior peut alors plus rapidement monter en compétence sur des sujets intéressants et profonds.

Inconvénients de TDD

  • Learning Curve gigantesque sans formation poussée et accélérée par un réel expert.
    Apprendre le TDD nécessite un arbre de compétences en guise de pré-requis :
    Notions avancées de Clean Code, de Refactoring, de doubles (mocks/stubs/spies), de culture en "courants existants" de TDD tels que London or Classicist school (mindsets), d'abstraction, indirection et découplage.
  • Nécessite un perfectionnisme sans faille
    Un simple trou dans la raquette, une baisse de motivation, une violation d'une des 3 lois et c'est tout l'intérêt du TDD qui s'envole.
    Tous les acteurs du projet doivent respecter la discipline scrupuleusement, autrement cela ne portera pas ses fruits.
  • Pratique très mal interprétée de nos jours par les acteurs de l'IT
    La plupart des Managers, Développeurs, MOA, QAs, QCs, et même coachs agiles ont une vision bien erronée de TDD, comme mentionnée plus haut, conduisant à l'effet totalement inverse à savoir un ralentissement drastique de la productivité, des tests trop fragiles (liés aux détails d'implémentation) et un surcoût indéniable.
    Il faut donc faire attention lors de la recherche de bons formateurs/mentors/coachs et viser la bonne littérature sur le sujet; chose qui n'est clairement pas encore répandue.

Pour finir, nous vous conseillons cette superbe vidéo de J. B Raisenberg de 7 minutes seulement sur le sujet mettant également en valeur le TDD comme solution viable et tout à fait réaliste à la complexité accidentelle.



Conclusion

Le TDD est un état d'esprit, un mindset, une discipline ultra rigoureuse.
Loin d'être une corvée, elle permet des avantages incontournables à tous les niveaux, et notamment sur la productivité à court, moyen et long terme.
Bien évidemment, il faut que la pratique soit maîtrisée par tous les développeurs ce qui n'est pas une mince affaire et tellement rare de nos jours.

Chez WealCome, nous insistons sur cette pratique car nous sommes conscients depuis si longtemps de ses bienfaits
avec une expérience riche et profonde ; tous nos projets respectant.

Nous recommandons avec ferveur les formations WealCome de Michaël AZERHAD sur le sujet.

Pour des démonstrations complètes de TDD en vidéo, parcourez notre blog.

Dans un prochain article, nous allons nous attaquer à une autre très mauvaise interprétation :
Penser qu'un test unitaire cible une fonction (même fine) et non un comportement ... aïe aïe aïe !

À bientôt, en espérant vous avoir intéressé au TDD !

Bon code à toutes et à tous !