Combiner TDD (Test-Driven Development) et LLM
- Le TDD est une méthodologie de développement qui consiste à écrire d’abord des tests unitaires complets avant de commencer à écrire le programme
- Comme les tests jouent de facto le rôle de spécification, le fait qu’ils passent tous permet au final de démontrer dans une certaine mesure la justesse du code
- Traditionnellement, le TDD est parfois critiqué comme nuisible à la productivité ou inefficace
- Mais avec l’arrivée des LLM, le processus d’écriture des tests et de révision itérative du code est devenu bien plus simple
Ma façon habituelle d’utiliser les LLM
- J’ai utilisé activement des outils comme GitHub Copilot
- Les LLM excellent pour repérer des motifs répétitifs et autocompléter les lignes suivantes, mais ils ont souvent du mal à comprendre en profondeur l’ensemble d’un problème pour produire d’un coup un module complet et cohérent
- Si on fournit trop de contexte nécessaire à la résolution du problème, le modèle a tendance à s’éloigner du sujet
- En avançant en ne fournissant que partiellement les informations nécessaires (comme la sortie d’erreur), le modèle devient aussi une excellente aide pour le débogage
- J’ai constaté qu’il y avait des frictions dans le va-et-vient répété entre l’IDE, le terminal et l’interface de chat avec le copier-coller
Peut-on automatiser cela ?
- Pour automatiser ce processus, j’ai moi-même introduit le concept d’event loop
- Si l’on précise dans le premier prompt la spécification de la fonction à implémenter et sa signature, le modèle propose une première ébauche des tests unitaires et du code
- Ce code est enregistré dans le répertoire « sandbox », puis
go test est exécuté automatiquement
- Si les tests échouent, un second prompt itératif est envoyé avec le code existant et les résultats des tests (erreurs de compilation ou informations d’échec)
- À partir de là, le modèle propose à nouveau des tests corrigés et un code d’implémentation révisé
- Ce processus est répété jusqu’à ce que tous les tests passent
- Cette approche permet une amélioration progressive sans accumuler excessivement de contexte
- Le modèle peut toutefois échouer de manière répétée sur le même cas de test ; dans ce cas, une intervention humaine pour pointer le problème et fournir un indice est utile
- Il faut être conscient du problème d’« absence de surveillant », qui conduit à s’interroger sur le caractère suffisamment rigoureux des tests générés par le LLM
- Le code et les tests peuvent partager ensemble la même erreur ou une conception incomplète
- Il est donc important qu’un humain renforce en plus les cas de test
- Si nécessaire, on peut aussi expérimenter avec l’IA des techniques comme le mutation testing
Développement basé sur les LLM et charge cognitive (cognitive load)
- En appliquant le TDD avec un LLM, cela semble possible non seulement sur des problèmes algorithmiques classiques, mais aussi dans des bases de code réelles avec des dépendances
- Cela suppose toutefois de découper davantage la structure du projet en unités plus petites pour améliorer la maintenabilité, et de faire en sorte que chaque répertoire/package puisse être testé indépendamment
- Pour réduire la charge cognitive, il est recommandé de séparer chaque package entre les principales définitions de types (
shared.go), les fichiers chargés d’une logique spécifique (x.go) et les tests (x_test.go)
- Lors de l’utilisation de l’IA, au lieu de fournir l’ensemble du code au modèle à chaque fois, on n’inclut sélectivement que certaines parties afin de l’aider à se concentrer
- Cela augmente la couverture de test tout en réduisant le couplage entre modules, ce qui présente aussi des avantages pour la maintenance à long terme
- Même pour les grands projets, l’objectif est une structure découpée en unités petites et claires, riches en logique interne mais à périmètre minimal
Conclusion
- Vu la vitesse d’évolution de l’IA, une nouvelle architecture pourrait apparaître dès demain et dépasser les limites actuelles des LLM
- Il est donc recommandé, plutôt que de refactorer brutalement un vaste code legacy de plus de 100 000 lignes, d’explorer d’abord à petite échelle les possibilités offertes par la combinaison du TDD et des LLM
- On peut s’attendre à ce que la fusion du TDD et des LLM apporte des changements positifs à la fois à la génération automatique de code et à la gestion de la qualité des tests
5 commentaires
Je me demande longuement quel type de pipeline utilisent les autres services d’IA dédiés au développement.
(Quand je vois ce genre de choses,) j’ai l’impression que d’ici peu, on finira par s’implanter des électrodes de stimulation dans le cerveau pour un cerveau cybernétique.
Ajouter du code de test semble être une bonne idée, mais j’ai l’impression que le programme créé par cette personne n’a pas vraiment d’intérêt.
Dans cline ou aider aussi, on peut exécuter des commandes en ligne de commande et récupérer les résultats, donc il me semble qu’il vaut mieux simplement bien formuler les prompts dans ces programmes si l’on prend en compte les autres aspects pratiques.
Le micro-agent créé par Builder.io adopte aussi une approche similaire. https://github.com/BuilderIO/micro-agent J’ai moi aussi essayé plusieurs fois LLM et TDD, et il faut aussi bien gérer l’abstraction, notamment avec un design system. Il faut également que les conventions et les patterns soient bien établis. En général, j’écris moi-même les cas de test. (Même en langage humain ?) Surtout, comme le dit aussi cet article, il faut bien concevoir des modules à faible couplage et forte cohésion afin de pouvoir faire tenir le contexte dans une fenêtre de contexte limitée.
Les LLM gèrent bien le code de petite portée, mais ils restent encore un peu limités sur la conception globale et la vision d’ensemble,
Donc l’approche qui consiste à les combiner avec le TDD pour améliorer les choses progressivement me semble bonne.