Contenu

Test Driven Development By Example - Kent Beck

Introduction

Après des années de pratique du TDD et la satisfaction d’en avoir tiré une amélioration nette de mon workflow quotidien, j’ai souhaité me plonger dans le classique de Kent Beck “Test Driven Development By Example”.
Test Driven Development qu’il a très fortement contribué à populariser au début des années 2000. Le livre date de cette période et a donc près de 20 ans. Mais ses leçons sont elles encore valables après toutes ces années ?

Je partage ici avec vous quelques notes prises pendant la lecture de ce livre.

/test-driven-development-by-example/couverture.jpg

Kent Beck commence par nous expliquer, dès la première page, les deux règles de la méthode :

  • Ecrire du nouveau code seulement si un test automatisé a échoué
  • Eliminer la duplication

On entend souvent parler des 3 lois du TDD qui sont énoncées comme ceci par Oncle Bob :

  1. Vous devez écrire un test qui échoue avant d’écrire le moindre code en production
  2. Vous ne devez pas écrire plus de tests que nécessaire pour échouer, et ne pas compiler revient à échouer
  3. Vous ne devez pas écrire plus de code de production que nécessaire pour faire passer le test qui a échoué

Sources :

Procéder de la sorte implique plusieurs choses comme un feedback rapide sur le code écrit et un design avec une forte cohésion et un coupage faible pour avoir des tests simples à mettre en place.

Et cela implique également une méthode de travail qui est la boucle bien connue Red/Green/Refactor :

Red : on écrit un test qui ne passe pas, car le code correspondant n’est pas encore implémenté ou même parce que ça ne compile pas.

Green : on le fait passer au vert en écrivant le minimum de code possible, quitte à proposer une solution naïve ou simpliste

Refactor : on élimine les duplications créées à l’étape précédente

/test-driven-development-by-example/cycle.png

De plus, mettre en place ce genre de pratiques, qui diminue le nombre de bugs et réduit drastiquement le nombre de surprises découvertes sur le tard, a des conséquences vertueuses sur le cycle de déploiement qui peut se raccourcir de façon substantielle.

Après avoir posé ces bases, Kent Beck nous explique que le TDD a cette particularité de permettre de mieux gérer sa peur pendant qu’on développe. La peur qui nous fait hésiter à démarrer notamment, qui nous tétanise par manque d’assurance sur ce qu’on produit.
Le TDD diminue ces peurs, et les élimine même, en permettant d’apprendre très vite de ce que l’on fait et en permettant d’obtenir très vite du feedback sur ce que l’on est en train de construire. Au fur et à mesure qu’on développe en pratiquant le TDD, on se sécurise et on découvre, de façon incrémentale, ce que l’on est en train de résoudre.

Enfin, il confie au lecteur que les exemples qu’il va présenter peuvent paraître simples, et loin de la réalité du quotidien avec des codebases complexes. C’est parfois ce qu’on entend quand on fait des katas dans les équipes :

“OK c’est sympa cette méthodologie, mais dans la vraie vie, le problème à résoudre n’est pas si simple et la complexité du code est bien plus grande.”

Il nous invite alors à raisonner dans l’autre sens : amener son propre code vers un code plus simple et plus propre. Et c’est cette promesse de simplicité que le TDD nous apporte.

Un premier exemple

Pour illustrer la méthodologie, Kent Beck prend l’exemple d’une application qui permet de calculer des factures et à laquelle il faut ajouter le support de différentes monnaies.

Il note dans une liste les tests qui lui semblent pertinents de prime abord, en découpant le plus finement possible les fonctionnalités attendues. Au fur et à mesure de son avancée dans le développement et finalement de la découverte de ce qu’il doit faire, il enrichit cette liste de ce qu’il découvre qu’il doit faire. Et raye également ce qu’il a déjà fait.

/test-driven-development-by-example/strike-text.gif

Kent Beck nous donne quelques principes pour faire passer ses tests au vert :

  • Faire retourner une constante par les méthodes. Constante qu’on remplace au fur et à mesure par le véritable code attendu
  • Implémenter directement la solution, car elle est triviale
  • Utiliser ce qu’il appelle la triangulation : ne généraliser le code que s’il a au moins deux tests autour de la fonctionnalité attendue

Il passe facilement de l’un à l’autre. Quand tout se passe bien, il implémente les choses directement, et puis, dès que les tests passent au rouge, il revient au principe d’utilisation de constante pour se sécuriser avant de revenir à son fonctionnement précédent.
D’ailleurs, si on associe souvent le TDD au principe des baby steps, Kent nous rappelle que c’est un processus à vitesse variable.
Quand on est serein et qu’on se trouve contraint par un processus trop lent, on peut accélérer. Et dès que c’est trop rapide, on révise sa vitesse à la baisse. Et on alterne comme ça au fur et à mesure de la construction.

On parle beaucoup ici d’ajout de nouvelles fonctionnalités, mais parfois il est nécessaire de refactorer du code existant pour ajouter lesdites fonctionnalités.
Kent nous invite alors, dans le cadre de ce refactoring et si des tests suffisants n’existent pas, à écrire les tests qu’on aurait aimé avoir pour le faire en toute sécurité. De toute évidence, ne pas le faire nous conduira inévitablement dans le mur.

En introduction, Kent parlait de la peur, il évoque ici celle de l’inconnu. Pour pallier cette peur, on peut réfléchir beaucoup, échafauder des hypothèses, les partager, consulter, bref, passer du temps pour parcourir des chemins qui ne seront peut-être pas les bons finalement. Alors qu’une solution s’offre à nous : demander à l’ordinateur, les tests et le TDD sont là pour ça !
Une idée ? Expérimentons-là !
Le feedback rapide amené par le TDD nous permet de savoir très vite si on est dans la bonne direction. Ça fonctionne ? Continuons ! Ça ne fonctionne pas ? OK, expérimentons autre chose !

/test-driven-development-by-example/labotournesol.gif

Il peut arriver parfois qu’au milieu d’un test rouge, on se rende compte qu’on a besoin de faire une modification dans du code ne correspondant pas au code qu’on souhaite faire passer au vert. Dans ce cas, Kent nous invite à être conservateur :

  1. rollbacker le code lié au test en cours pour revenir au vert
  2. faire la correction dans le code ayant besoin d’être modifié (en le couvrant par des tests évidemment)
  3. et une fois cela fait, revenir au test initialement identifié.

Méthodologie d’écriture des tests

Kent nous indique qu’on peut faire des faux départs quand on construit quelque chose, et qu’il ne faut pas hésiter à supprimer le code créé si on sent qu’on est parti dans une mauvaise direction pour revenir en arrière. Ça fait partie du processus de découverte inhérent au TDD.

Il nous invite aussi à faire très attention au couplage des tests. Il ne faut pas que des tests dépendent les uns des autres, notamment en termes d’ordre d’exécution. C’est un fort point de vigilance.

Enfin, concernant les refactorings effectués tout au long du processus de développement, il peut arriver qu’il faille parfois les défaire et revenir en arrière. On peut adopter différentes stratégies : attendre de voir un pattern apparaître au bout de 3/4 répétitions de code pour refactorer afin de se “garantir” (tout est relatif …) qu’on n’aura pas à les défaire ou on peut tout à fait les faire au fil de l’eau (stratégie privilégiée par Kent Beck).

Patterns de TDD et/ou de refactoring

Dans cette dernière partie, Kent expose quelques bonnes pratiques et autres patterns de TDD ou de refactoring.

Je ne détaillerai pas l’utilisation des différents design patterns et refactorings exposés, mais vous invite plutôt à aller regarder les quelques références disponibles sur le sujet :

Design Patterns - Elements of Reusable Object-Oriented Software - Gang of Four

Head first Design Patterns - Elisabeth Freeman, Eric Freeman

Refactoring - Improving the Design of Existing Code - Martin Fowler

Working Effectively with Legacy Code - Michael Feathers

Quelques généralités que je retiens parmi les conseils exposés ici :

  • rendre l’exécution des tests la plus rapide possible afin de diminuer au maximum la boucle de feedback

  • commencer par écrire l’assertion quand on écrit un test, ça permet d’avoir une phase de setup la plus simple et la plus adaptée au test possible

  • vous êtes perdus dans votre code ? Jetez-le et recommencez !

  • quand on développe une fonctionnalité tout seul, il est conseillé de terminer sa session en laissant un test qui ne passe pas. Ainsi, à la reprise du code, on saura exactement où en était et on reprendra vite le chemin du cercle vertueux qu’on avait initié. Attention, la règle s’inverse si on travaille en équipe sur la même fonctionnalité : il est alors plus judicieux de terminer sa session sur un test qui passe.

  • traiter le code de test comme le code de production (clean code, nommage…). Ne jamais oublier que vous écrivez ces tests d’abord pour des humains et non pour la machine ou pour vous-même !

  • soyez prudents avec la tentation de l’implémentation directe de la solution, ça peut vous conduire à vouloir faire deux choses à la fois : du code propre et du code qui fonctionne. Il vaut vraiment mieux faire les choses dans l’ordre : faire fonctionner et ensuite rendre le code propre (“make it work, make it right, make it fast”)

Un conseil qui peut paraître surprenant dans un ouvrage technique, mais auquel je souscris complètement : faites des pauses ! Votre fatigue altère votre jugement, qui affecte lui-même votre fatigue, c’est réciproque.

/test-driven-development-by-example/nap-time.gif

Conclusion(s)

Sur son approche

Pour conclure cette lecture, je rappelle l’essentiel résumé par Kent Beck : avancer à petits pas, recueillir du feedback, continuer.

La question de ce qu’il ne faut pas tester et où on doit s’arrêter revient souvent. La maxime suivante est alors évoquée : “Ecrivez des tests jusqu’à ce que la peur se transforme en ennui”.

Quelques code smells évoqués par Kent Beck qui indiquent un souci dans les tests :

  • une longue phase de setup
  • de la duplication dans le setup
  • des tests longs à exécuter
  • des tests fragiles

Tout ceci, ensemble ou séparément, évoque un problème de découpage/design/dispersion de responsabilité mal orchestré dans le code.

En pratiquant le TDD, en ne considérant pas le futur du code, mais en répondant seulement au besoin du jour, on rend finalement le code bien plus adaptable aux évolutions futures, qui sont inconnues par essence.

Quant à la qualité et au nombre de tests à réaliser, c’est l’expérience qui permettra d’écrire le bon nombre de tests. Donc pour apprendre à mieux tester, testez !.

Enfin, un rappel important et ce qu’il faut retenir de cette lecture : pratiquer le TDD va contribuer à réduire la peur de modifier du code, d’en ajouter, ce qui contribuera ainsi à réduire le stress du développeur.
Le TDD va également permettre d’aboutir à un design simple et facilement modifiable/adaptable à des besoins futurs. De plus, pratiquer le TDD permet de se concentrer sur une seule chose à la fois et de bien la faire. C’est un cercle vertueux qui se met en place par cette pratique.

Une précision importante apportée par Kent Beck en toute fin de livre : le TDD n’est pas une stratégie de test, mais bel et bien une stratégie de design !

Un avis plus personnel

La lecture du livre de Kent Beck est encore passionnante et pleine d’enseignements aujourd’hui.

Evidemment, les passages qui évoquent l’utilisation de JUnit ou le manque d’outils de refactoring datent un peu, le livre ayant 20 ans. Aujourd’hui, on a des IDE (IntelliJ notamment) qui proposent des outils de refactorings rapides et puissants.

Par mes échanges au sein de la communauté rennaise, par mon travail au quotidien, je constate que l’essence du TDD, bien que connue à gros grains par un grand nombre de développeurs, n’est finalement mise en pratique que par un petit nombre d’entre eux.

Avec souvent les mêmes remarques : “je ne sais pas quoi tester”,“je ne sais pas par où commencer”,“ce n’est pas moi qui fais les tests de mon code” (!!!)

Il n’y a pourtant pas plus simple pour apprendre à mieux tester et au final à mieux designer ses applis : se lancer !

Et vous n’avez même pas besoin de demander l’autorisation si le livrable qui vous est demandé c’est “le code + les tests” : construisez vos tests comme vous voulez.

Bref, lancez-vous 😉

Et lisez le livre de Kent Beck, il ne fait que 200 pages et se lit vite.

Et si vous ne savez vraiment pas comment faire, venez discuter avec la communauté rennaise au meetup Software Craftsmanship, vous y trouverez des passionnés bienveillants qui seront ravis de vous guider : https://www.meetup.com/fr-FR/Software-Craftsmanship-Rennes/

Pour terminer : amateurs/pratiquants du TDD, il y a encore beaucoup de travail d’évangélisation à faire ! Novices, allez à la rencontre des passionnés qui sauront vous guider !

Merci à Annabel et David pour la relecture