Cet article est publié sous licence CC BY-NC-SA
Ah le TDD ! Le sujet revient souvent sur le devant de la scène, dans des échanges informels ou des débats enflammés entre développeurs de tout poil. Et ses plus fervents défenseurs argumentent habituellement la chose de la sorte :
Dans MaBoite on fait du TDD ! Pour preuve, on a des tests unitaires, des tests d’intégration, des tests end-to-end… Notre couverture de test ? Oh, facilement 80-90% sur tous nos projets ! D’ailleurs c’est pas compliqué, c’est même écrit dans nos guidelines et nos offres d’emploi !
Seulement voilà, ici sont confondues deux pratiques : écrire des tests automatisés et guider l’écriture du code à l’aide de tests automatisés. Test Driven Development. Dans le premier cas, il est facile de court-circuiter l’étape d’écriture de tests (pour tout un tas de raisons, toutes plus ou moins pertinentes selon le contexte, toutes plus ou moins acceptables selon le locuteur) ; alors que dans les seconds cas, sans tests il n’y a pas de code.
Et le taux de couverture du code par les tests est un sujet à part entière, dont il faut retenir que cette valeur seule n’est qu’une valeur, elle n’est donc pas autorévélatrice, elle se doit d’être interprétée.
Je vous invite ainsi, cette semaine, à prendre la mesure de ce qu’est la pratique du TDD, quels sont ses enjeux et ses promesses.
Dans un monde fictif, un développeur fictif d’une entreprise fictive attaque une nouvelle feature fictive…
Alors on y va ! Devant l’écran, on a une vague idée du truc à coder, parfois avec des captures photo d’un tableau blanc abordé en réunion. Les puristes auront fait un joli schéma, mais le résultat sera le même. On démarre directement une classe et quelques méthodes dans les grandes lignes pour avoir un squelette de la logique recherchée. On voit pas trop le bout et on commence à griffonner des choses dans un cahier à côté.
Là, on se souvient qu’il faut écrire les tests avant d’avoir le code. Ah zut, je vais perdre du temps mais bon, il paraît que c’est une bonne pratique. Alors l’étape n°2 est d’écrire une série de tests, pour plus tard, qui échouent tous bien évidement. Ils sont pour la plupart de haut niveau, taillés pour coller au code déjà écrit et n’offrent aucune vision du besoin métier. Mais qu’importe. On se dit que c’est ça le TDD.
Au bout de quelques itérations de développement (plusieurs semaines ou mois), la lecture des tests devient compliquée et on finit par avoir des échecs énigmatiques. Dans le meilleur des cas, c’est aléatoire. Dans d’autres, c’est systématiquement les mêmes tests qui échouent mais personne n’a le courage de plonger dans le code pour fixer les tests. Des collègues agacés finiront par mettre en commentaire les tests en erreur.
Le plus bizarre reste quand même que, malgré les tests, les utilisateurs nous remontent des bugs… Mais ne faisons pas plus longtemps l’apologie de mauvaises pratiques.
À présent, j’ai une bonne et une mauvaise nouvelle.
La bonne nouvelle, c’est qu’un tourne vis est aussi utile à un électricien qu’à un plombier. Ici, le tourne vis, ce sont les tests automatisés. Et ce qui est bien, c’est que vous avez déjà l’outil.
La mauvaise nouvelle, c’est que si votre pratique ressemble à celle exposée ci-dessus, l’outil vous l’utilisez mal. L’approche décrite dans cet exemple fictif est nommée « test first », qui est un faux-ami du TDD. Elle donne une fausse impression de sécurité et n’apportera rien à la conception logicielle.
Alors, reprenons au début.
L’image qui me vient en tête pour ce principe, je la dois à Michaël Azerhad : le TDD, c’est comme un GPS. Il vous guide vers votre destination. Sans lui, vous risquez de longs moments d’errance et de parcourir inutilement un chemin tortueux… Dans l’hypothèse où vous trouvez au final la destination, évidement. Inutile de préciser qu’avec un peu de pratique, TDD fait vraiment gagner du temps.
TDD s’inscrit dans la mouvance de l’Extreme Programming et a été formalisé par Kent Beck dans son ouvrage « Test Driven Development : By Example ».
Son but est de découvrir une implémentation propre, répondant à un problème donné, par itérations successives. La méthode vise aussi bien la qualité du code que de réduire le stress sur le développeur en le sécurisant pendant la phase de développement. Les itérations visent à raisonner en découpant la complexité en unités plus petites.
TDD ne vous donne pas la solution au problème mais un cadre pour vous permettre d’atteindre la solution. Je le redis : lorsqu’on débute une session en TDD, nous ne connaissons pas la solution. Si c’est le cas, vous avez sûrement un biais qui pourra amener une complexité inutile. En revanche, on a une idée générale de la direction à prendre (mais aucun code pour valider cette direction).
Donc, avant de coder quoi que se soit, vous rangez le clavier et prenez conscience de la complexité de la tâche. Ensuite, vous pourrez tacler le problème en unités plus petites pour lesquelles vous pourrez vous projeter une représentation mentale et commencer votre route vers la solution.
Voici le déroulé :
Vous n’aurez peut-être pas la solution la plus optimisée, mais vous serez arrivé à destination. Rien ne vous empêche ensuite d’optimiser l’implémentation.
Lorsqu’on quitte une session de développement, on se laisse un test en rouge pour savoir où reprendre à notre retour. À l’inverse, lorsqu’on partage un développement (pour une revue ou autre), tous les tests doivent être au vert.
Kent Beck donne 2 règles pour pratiquer :
Pour ce second point, ne cherchez pas forcément des lignes identiques ou qui se rapprochent. L’intention est bien plus importante que la forme (je vous ramène à notre article sur DRY : duplication ou coïncidence). Et n’oubliez pas d’appliquer cette logique aux tests eux-mêmes, mais ne modifiez pas tout en même temps : soit vous modifiez les tests, soit vous modifiez le code — mais pas les deux en même temps.
Quelques années après, Oncle Bob (Robert C. Martins) a étendu la vision d’origine avec ses « 3 lois de TDD » qui concernent surtout la règle #1 de Kent Beck :
On retrouve ici l’essence même des règles de Kent Beck, mais la vision d’oncle Bob va un peu plus loin en nous imposant d’avancer étape par étape. Cette façon de progresser est essentielle. Si on brûle les étapes, on perd le cap et la durée du développement s’allonge.
Ce qui sous-entend d’écrire du code simple — parfois idiot — pour répondre à un test. Ceci veut dire que l’implémentation n’est pas toujours correcte, mais vous devez avancer ensuite avec d’autres tests. Tenez-vous les 10 doigts pour ne pas enchaîner un if
imbriqué si vous n’avez pas commencé par passer un test via un if
/ else
simple. Inutile de mentionner que si vous débutez votre code avec un case
, vous avancez trop vite.
Évitez également de créer des abstractions en phase d’écriture d’un code pour répondre à un test. On ne voit l’évidence d’une abstraction qu’en phase de factorisation et à condition d’avoir réellement deux éléments (à minima) qui auraient mérité une abstraction. Si vous n’êtes pas certain, laissez en l’état pour avancer. Vous aurez largement le temps de vérifier vos hypothèses aux prochaines phases de factorisation et vous évitez une abstraction inutile qui vas compliquer vos prochaines étapes.
Il y a bien d’autres sujets autour du TDD, et nous n’avons vu que l’introduction. Si ce sujet vous passionne (et il le devrait), je vous recommande la lecture de l’ouvrage d’origine de Kent Beck. TDD vous permet à la fois de progresser sereinement et avec le bon rythme.
L’équipe Synbioz.
Libres d’être ensemble.
Nos conseils et ressources pour vos développements produit.