TDD is dead. Long live testing.

David Heinemeier Hansson
David Heinemeier Hansson

Quand on est adepte d'une façon de faire, il est important d'écouter ce que les détracteurs de cette pratique ont à dire.

Parmi les détracteurs du Test-driven development (TDD), une voix compte particulièrement, celle de David Heinemeier Hansson. Tout le monde l'appelle DHH, c'est plus smart.

Un personnage aux multiples facettes, créateur de Ruby on Rails et vainqueur de la 82ème édition des 24 Heures du Mans, entre autres faits d'armes. Ses idées iconoclastes m'interpellent, à défaut d'y adhérer.

En 2014, son article TDD is dead. Long live testing. a fait date. DHH y exprime sa lassitude de se sentir montré du doigt parce qu'il n'est pas adepte du TDD.

Les mots sont forts : "fondamentalisme", "religion". On sent qu'il en a gros sur la patate.

Reprenons donc ses arguments point à point. C'est un peu ma "réponse à DHH", même si je doute que le monsieur attende quelque réponse de ma part.

[TDD is] used as a hammer to beat down the nonbelievers, declare them unprofessional and unfit for writing software.

Effectivement, ça n'a aucun sens de montrer les gens du doigt. Ça fait mal, et c'est à l'opposé de l'humilité chère aux artisans du logiciel. Garder à l'esprit que je peux me tromper, et que ce qui est bon pour moi ne l'est pas forcément pour un autre.

Difficile de lui donner tort, d'ailleurs, car le résultat compte avant toute chose. S'il produit du "bon code" sans TDD, parfait ! Glenn Gould n'était pas le plus académique des pianistes — euphémisme, et pourtant… quel interprète ! (*)

Par contre, reste à définir ce qu'est du "bon code". Au-delà de l'absence de bugs, cela signifie pour moi disposer de tests, et pas que de tests à l'échelle du système dans son ensemble. Je veux des tests unitaires, qui décrivent le comportement de l'application exhaustivement et au plus bas niveau possible.

Ces tests documentent le code, sont co-localisés avec ce dernier et jouent le rôle de filet de sécurité. Plus les mailles sont fines, mieux on évite les régressions.

Step two is to rebalance the testing spectrum from unit to system. […] I rarely unit test in the traditional sense of the word, where all dependencies are mocked out, and thousands of tests can close in seconds.

La pratique du TDD s'entend effectivement dans le cadre de tests unitaires. Le TDD ne dit rien des tests de plus haut niveau (tests d'intégration, tests d'acceptance, tests e2e etc.), et surtout le TDD ne dit pas qu'il n'y a rien en dehors du TDD !

Sauf que des tests du système dans son ensemble ne sauraient remplacer des tests unitaires. Je ne vais pas tester tous les cas d'une fonction qui calcule l'impôt sur le revenu au travers de tests end-to-end, qui sont aussi longs à écrire qu'à exécuter. Cela n'aurait aucun sens.

Chaque type de tests apporte sa pierre à l'édifice. Les tests unitaires offrent un feedback très rapide (quelques millisecondes), et c'est intéressant à double titre :

  • Le feedback est ce qui permet de se laisser guider par les tests dans l'écriture de code ;
  • Plus un bug est détecté tôt, moins il coûte cher.

It's given birth to some truly horrendous monstrosities of architecture. A dense jungle of service objects, command patterns, and worse.

Je ne connais aucune de ces complications, aucun de ces effets secondaires indésirables quand je fais de la programmation fonctionnelle. Est-ce donc un effet du TDD ou bien de la Programmation Orientée Objet ?

En revanche quand, par trop d'injection de dépendances, on en vient à coupler le test à l'implémentation et à "tester l'implémentation", oui il faut lever le stylo. Mais encore une fois, le problème est-il lié au TDD ou bien à l'architecture hexagonale (à laquelle on peut préférer le functional core / imperative shell) ?

Enough. No more. My name is David, and I do not write software test-first.

Dernière chose, et pas des moindres : tout au long de son article, DHH évoque le "Test first", quand le titre promet de parler de TDD. Ce sont pourtant 2 choses complètement différentes :

  • Test-first : j'écris tous les tests avant d'écrire la moindre ligne de code ;
  • Test-driven development : les tests me guident dans l'écriture de code, donc j'écris à chaque fois un peu de code "en réaction" à un nouveau test.

Dommage que cette confusion ne soit jamais levée, car elle cache une tout autre façon de programmer.

Comme toujours, soyons pragmatiques : le TDD est un outil, un outil parmi d'autres. Il doit aider. S'il n'aide pas, c'est que ça n'est pas le bon outil pour ce problème.

Le TDD se prête particulièrement bien au code du domaine, le noyau fonctionnel d'un bounded context. Le cœur de l'hexagone, le cœur du réacteur. Un moteur de calcul, des règles de gestion fines, des cas limites à tout-va. Là, je ne sais pas faire autrement qu'en TDD. Mais au final, cela ne représente qu'une petite partie de la codebase, 30% au plus.

Au final, je ne partage presque aucune des conclusions de DHH, mais ses arguments auront eu le mérite de m'inciter à mieux cerner les contours de ma pratique du TDD.

Le TDD n'est pas une religion, c'est un outil 😊

(*) Pour tenter de vous en convaincre : https://www.youtube.com/watch?v=qEHsDe5Vaxg

Subscribe to Mathieu Eveillard

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe