J'ai récemment donné une conférence sur le débogage pour la communauté Java de Londres. Au cours de la partie questions-réponses de la conférence, quelqu'un m'a posé des questions sur mon approche du développement piloté par les tests. Dans le passé, je regardais cette pratique sous un jour plus positif. Rédaction de nombreux tests. Comment cela peut-il être mauvais ?
Mais au fil du temps, je le vois sous un jour différent.
Je le vois comme un outil très limité qui a des cas d'utilisation très spécifiques. Cela ne correspond pas au type de projets que je construis et entrave souvent les processus fluides qu'il est censé promouvoir. Mais revenons en arrière une seconde. J'ai beaucoup aimé ce post qui sépare les types et les problèmes en TDD. Mais simplifions un peu, précisons que chaque PR doit avoir une bonne couverture. Ce n'est pas TDD. C'est juste une bonne programmation.
TDD est plus que cela. Dans celui-ci, nous devons définir les contraintes puis résoudre le problème. Cette approche est-elle supérieure à la résolution du problème puis à la vérification que les contraintes sont correctes ? C'est le principe de base de TDD par rapport à la simple écriture d'une bonne couverture de test.
TDD est une approche intéressante. C'est particulièrement utile lorsque vous travaillez avec des langages faiblement typés. Dans ces situations, TDD est merveilleux car il remplit le rôle d'un compilateur strict et d'un linter.
Il y a d'autres cas où cela a du sens. Lorsque nous construisons un système qui a des entrées et des sorties très bien définies. J'ai rencontré beaucoup de ces cas lors de la création de cours et de matériel. Lorsque vous travaillez sur des données du monde réel, cela se produit parfois lorsque nous avons un middleware qui traite les données et les sort dans un format prédéfini.
L'idée est de construire l'équation avec les variables cachées au milieu. Ensuite, le codage devient le remplissage de l'équation. C'est très pratique dans des cas comme ça. Le codage devient le remplissage des blancs.
“Test Driven Development IS Comptabilité en partie double. Même disciplines. Même raisonnement. Même résultat. – Oncle Bob Martin
Je dirais que les tests sont un peu comme une comptabilité en partie double. Oui. On devrait faire des tests. La question est de savoir si nous devons construire notre code en fonction de nos tests ou vice versa ? Ici, la réponse n'est pas si simple.
Si nous avons un système préexistant avec des tests, alors TDD prend tout son sens dans le monde. Mais tester un système qui n'a pas encore été construit. Il y a des cas où cela a du sens, mais pas aussi souvent qu'on pourrait le penser.
La grande revendication de TDD est « sa conception ». Les tests sont effectivement la conception du système, et nous mettons ensuite en œuvre cette conception. Le problème avec cela est que nous ne pouvons pas non plus déboguer une conception. Dans le passé, j'ai travaillé sur un projet pour une grande entreprise japonaise. Cette société possédait l'un des ensembles les plus importants et les plus détaillés de manuels de conception d'annexes. Sur la base de ces spécifications de conception, l'entreprise a construit des milliers de tests. Nous étions censés passer une énorme quantité de tests avec notre système. Notez que la plupart n'étaient même pas automatiques.
Les tests avaient des bugs. Il y avait de nombreuses implémentations concurrentes mais aucune d'entre elles n'a trouvé les bogues dans les tests. Pourquoi? Ils utilisaient tous le même code source d'implémentation de référence. Nous avons été la première équipe à sauter cela et à faire une implémentation en salle blanche. Il a perpétué ces bogues dans le code, dont certains étaient de graves bogues de performances qui affectaient toutes les versions précédentes.
Mais le vrai problème était la lenteur des progrès. L'entreprise ne pouvait pas avancer rapidement. Les partisans du TDD seront prompts à commenter qu'un projet TDD est plus facile à refactoriser puisque les tests nous donnent la garantie que nous n'aurons pas de régressions. Mais cela s'applique aux projets avec des tests effectués après coup.
TDD se concentre fortement sur les tests unitaires rapides. Il n'est pas pratique d'exécuter des tests d'intégration lents ou des tests de longue durée qui peuvent s'exécuter pendant la nuit sur un système TDD. Comment vérifier l'échelle et l'intégration dans un système majeur ?
Dans un monde idéal, tout se mettra en place comme des legos. Je ne vis pas dans un tel monde, les tests d'intégration échouent mal. Ce sont les pires échecs avec les bogues les plus difficiles à suivre. Je préférerais de beaucoup avoir un échec dans les tests unitaires, c'est pourquoi je les ai. Ils sont faciles à réparer. Mais même avec une couverture parfaite, ils ne testent pas correctement l'interconnexion. Nous avons besoin de tests d'intégration et ils trouvent les bogues les plus terribles.
En conséquence, TDD met trop l'accent sur les tests unitaires « agréables à avoir », au détriment des tests d'intégration essentiels. Oui, vous devriez avoir les deux. Mais je dois avoir les tests d'intégration. Ceux-ci ne s'intègrent pas aussi clairement dans le processus TDD.
J'écris les tests de la manière que je choisis au cas par cas. Si j'ai un cas où il est naturel de tester à l'avance, je l'utiliserai. Mais dans la plupart des cas, écrire le code en premier me semble plus naturel. Passer en revue les chiffres de couverture est très utile lors de la rédaction de tests et c'est quelque chose que je fais après coup.
Comme je l'ai mentionné précédemment, je vérifie uniquement la couverture des tests d'intégration. J'aime les tests unitaires et le suivi de la couverture car je veux une bonne couverture là aussi. Mais pour la qualité, seuls les tests d'intégration comptent. Un PR a besoin de tests unitaires, peu m'importe si nous les avons écrits avant l'implémentation. Nous devrions juger les résultats.
Lorsque Tesla construisait ses usines de modèle 3, il est entré dans l'enfer de la production. La source des problèmes était leur tentative de tout automatiser. Le principe de Pareto s'applique parfaitement à l'automatisation. Certaines choses sont tout simplement très résistantes à l'automatisation et rendent l'ensemble du processus bien pire.
Un point où cela échoue vraiment est dans les tests d'interface utilisateur. Des solutions telles que Selenium, etc. ont fait d'énormes progrès dans les tests des frontaux Web. Pourtant, la complexité est énorme et les tests sont très fragiles. Nous nous retrouvons avec des tests difficiles à maintenir. Pire encore, nous trouvons l'interface utilisateur plus difficile à refactoriser car nous ne voulons pas réécrire les tests.
Nous pouvons probablement croiser 80% des fonctionnalités testées, mais il y a un point de rendements décroissants pour l'automatisation. Dans ces environnements, TDD est problématique. La fonctionnalité est simple mais construire les tests devient intenable.
Je ne suis pas contre le TDD mais je ne le recommande pas et effectivement je ne l'utilise pas. Quand il est logique de commencer par un test, je peux le faire, mais ce n'est pas vraiment TDD. Je juge le code en fonction des résultats. TDD peut fournir d'excellents résultats, mais il met souvent trop l'accent sur les tests unitaires. Les tests d'intégration sont plus importants pour la qualité à long terme.
L'automatisation est excellente. Jusqu'à ce que ça s'arrête. Il y a un moment où les tests automatisés n'ont plus aucun sens. Cela nous ferait économiser beaucoup de temps et d'efforts pour accepter cela et concentrer nos efforts dans une direction productive.
Cela vient de mon parti pris en tant que développeur Java qui aime les langages stricts et sécurisés. Des langages tels que JavaScript et Python peuvent bénéficier d'un plus grand volume de tests en raison de leur flexibilité. Par conséquent, TDD a plus de sens dans ces environnements.
En résumé, les tests sont bons. TDD ne fait cependant pas de meilleurs tests. C'est une approche intéressante si elle fonctionne pour vous. Dans certains cas, c'est énorme. Mais l'idée que TDD est essentiel ou même qu'il améliorera considérablement le code résultant, n'a pas de sens.
Également publié ici.