Qui écrit quoi ? Répartir les rôles entre humain, IA et outils déterministes
Article 3 de la série “De la spécification à l’exécution”. L’article précédent traitait de la spec exécutable, c’est-à-dire de la manière de formaliser une intention sans ambiguïté. Ici, je passe au point suivant. Une fois la spec posée, qui écrit quoi ?
La question utile n’est pas de savoir si l’IA remplace les développeurs. Je n’y gagne rien pour le travail quotidien. La question qui compte est plus simple. Dans le pipeline qui va de la spec au code, qui écrit les tests, qui écrit le code, et qui vérifie que la suite ne s’est pas dégradée en route ?
Beaucoup d’équipes laissent un agent unique écrire les tests et le code dans le même mouvement. Le résultat est souvent propre en apparence, puis fragile dès qu’on regarde de près. Dans l’article 1, j’ai décrit trois dérives fréquentes, suppression de tests, over-mocking, tests perpetually green. Je pars de là.
Mon point est direct. Le test critique ne doit pas venir du même acteur que le code qu’il contraint. Si je veux garder une contrainte réelle, il me faut au moins deux rôles séparés, et parfois trois. L’humain, l’IA, et les outils déterministes n’ont pas le même rôle.
Le test comme spec exécutable
Dans son workflow augmented coding (2025), Kent Beck donne à son agent une instruction précise. L’agent doit prendre le prochain test non coché dans plan.md, l’implémenter, puis écrire le minimum de code pour le faire passer. L’agent ne choisit pas le test. Il l’exécute. Le test précède le code, et il vient d’ailleurs que l’agent.
Ce détail change le workflow. Deux travaux de 2024 et 2025 vont dans le même sens. Test-Driven Development for Code Generation (TiCoder, arxiv 2402.13521) montre qu’itérer sur tests et code avec un LLM améliore la qualité du résultat. Tests as Prompt: A TDD Benchmark for LLM Code Generation (TENET, arxiv 2505.09027) va plus loin. Sur des dépôts réels, donner les tests à l’IA dans le prompt atteint l’état de l’art pour la génération de code guidée par les tests.
Je retiens une règle simple. Le test n’est pas un sous-produit du code. C’est la contrainte qui guide le code.
La conséquence suit immédiatement. Si l’agent écrit le test et le code, il n’y a plus de contrainte indépendante. Il vérifie surtout qu’il fait ce qu’il vient de faire. C’est un circuit fermé, pas un workflow utile.
Il reste donc trois sources possibles pour le test, moi, un autre agent IA, ou un outil déterministe.
Les outils déterministes gardent leur place
La discussion sur l’IA fait parfois oublier des outils plus anciens, sans modèle, qui génèrent déjà des tests depuis du code, une signature ou une spec. Je les garde en tête parce qu’ils couvrent un terrain où l’IA est moins bonne.
Search-based
EvoSuite en Java, Pynguin en Python, et Randoop en Java explorent l’espace d’entrée avec des algorithmes évolutionnaires ou un retour aléatoire dirigé par les résultats. Ils cherchent surtout la couverture, branches, paths, mutation score.
Je les trouve utiles pour verrouiller des régressions sur du code legacy peu testé. Leur limite est simple. Ils génèrent ce que le code fait, pas ce qu’il devrait faire. Si le code contient un bug, ils peuvent tester le bug avec application.
Symbolic et concolic execution
KLEE pour C et C++ sur LLVM bitcode, angr pour les binaires, et leurs cousins exécutent le code symboliquement, génèrent des contraintes par chemin, puis résolvent ces contraintes avec un solveur SMT pour produire des entrées concrètes.
Leur force est nette. Ils donnent des garanties formelles, sont déterministes et se reproduisent bien. Je les garde surtout pour la sécurité, les chemins atteignables, les vulnérabilités, les entrées pathologiques. Leur limite est connue, l’explosion combinatoire des chemins les fait décrocher sur des bases applicatives larges.
Model-based testing
GraphWalker, ModelJUnit et SpecExplorer suivent une autre logique. Je décris le système comme un modèle, souvent une machine à états avec ses transitions, puis l’outil génère des séquences de tests selon une stratégie donnée, random walk, all-edges, all-paths.
Je les trouve sous-utilisés dans l’applicatif moderne. Pourtant, ils ont fait leurs preuves pendant des années en télécom, notamment sur des commutateurs et des systèmes où l’état compte plus que le cas nominal.
Spec-driven property-based testing
Le cas le plus intéressant pour mon workflow est aussi le plus sobre. Hypothesis ghostwriter en Python, icontract-hypothesis, ou encore les dérivations de QuickCheck en Haskell génèrent des squelettes de tests à partir des types, des signatures et des contrats. Il n’y a pas d’IA ici, seulement des templates, de l’inférence de types et quelques tables de correspondance.
Une commande comme celle-ci donne le ton :
hypothesis write mon_module.parse_sip_uri
Ghostwriter inspecte la signature, les annotations de type et le docstring, puis produit une base de tests property-based. Je peux ensuite la compléter avec des invariants utiles. C’est déterministe, reproductible, et adapté aux modules critiques où je ne veux pas d’invention.
Avec icontract, les préconditions et postconditions deviennent elles aussi exploitables. L’outil génère des tests qui respectent la précondition et vérifient la postcondition. Là encore, je gagne surtout en contrôle.
Pourquoi on en parle moins
Je vois trois raisons.
La première est le coût d’amorçage. Modéliser une machine à états, expliciter des contrats, préparer le terrain, tout cela demande du temps.
La deuxième est plus structurelle. Ces outils génèrent de la couverture, pas du sens. Il faut toujours une spec métier au-dessus.
La troisième est plus simple. Dans le débat public, l’IA occupe l’espace et ces outils sont passés au second plan. C’est dommage, parce qu’ils restent souvent plus fiables sur leur domaine.
La matrice tâche × auteur
Je résume le partage dans une grille simple. Trois acteurs, l’humain, l’IA, les outils déterministes.
| Tâche | Humain | IA writer-agent | Déterministe |
|---|---|---|---|
| Spec et intention métier | ✅ | – | – |
| Test d’acceptance, Gherkin | ✅ valide | ✅ propose | – |
| Test unitaire, cas concrets | ✅ écrit ou valide avant le code | ✅ propose | – |
| Test property-based, invariants | ✅ propose les invariants | ✅ co-écrit | ghostwriter pour le squelette |
| Coverage structurel | – | – | ✅ EvoSuite, Pynguin |
| Tests de machine à états | ✅ modèle | – | ✅ GraphWalker, ModelJUnit |
| Tests sécurité, chemins | – | – | ✅ KLEE, angr |
| Code de production | – | ✅ | – |
| Refactoring | ✅ valide | ✅ propose et exécute | – |
| Critique des tests | ✅ | ✅ critic-agent | ✅ mutation, lint, coverage gate |
Je tire trois règles de cette grille.
Le test critique vient de moi ou d’un outil déterministe. Je ne le laisse pas au même agent que le code qu’il contraint. Sinon je retombe sur les tautologies vues dans l’article 1.
Le code de production vient de l’IA, sous contrainte. Ce n’est pas l’agent qui propose une forme libre, puis la valide lui-même. C’est l’agent qui implémente un cadre déjà posé.
Le critic est un acteur à part entière. Il ne sert pas à décorer le pipeline. Il sert à éviter que le writer-agent transforme un test en confirmation de ce qu’il fait déjà.
Le pattern writer + critic
L’image de l’agent unique est encore fréquente, mais elle me semble déjà datée. Ce qui marche mieux, dans les travaux récents comme dans ma pratique, c’est une séparation claire entre celui qui écrit et celui qui critique.
Agent-as-a-Judge
Les travaux de Meta en 2024 vont dans ce sens. Un agent évalue un autre agent en regardant la chaîne complète d’actions, pas seulement le résultat final. Appliqué à la génération de tests, cela veut dire que le critic-agent ne se contente pas de voir si le test passe. Il regarde comment le test a été conçu, quels mocks ont été ajoutés, et si la spec est encore visible.
Vérification multi-agent
Multi-Agent Verification: Scaling Test-Time Compute with Multiple Verifiers (arxiv 2502.20379, 2025) propose des verifiers spécialisés, sans entraînement supplémentaire, chacun centré sur un aspect différent, sémantique, sécurité, performance, style. Pour des tests, le principe se transpose bien. Un verifier sur la couverture, un autre sur les oracles, un troisième sur la conformité à la spec Gherkin, un quatrième sur les anti-patterns. Le vote final tranche.
Débat multi-agent
Multi-Agent Debate for LLM Judges (arxiv 2510.12697, 2025) montre que le débat entre agents opposés améliore la qualité des jugements. Je peux le traduire ainsi dans mon workflow. Un agent défend la qualité du test, un autre cherche ses failles, puis un juge tranche. Je gagne en précision par rapport à un seul agent qui s’auto-répond.
En pratique
Le schéma qui me paraît le plus robuste ressemble à ceci :
Spec (moi) -> writer-agent -> critic-agent -> judge déterministe -> validation humaine
Le critic-agent applique des règles dures, comme celles que j’ai versionnées dans le repo public. Il ne modifie pas un test pour faire passer le code. Il refuse les mocks sur du code interne quand ils cachent le comportement réel. Il signale les oracles tautologiques.
Le judge déterministe, lui, ne discute pas. Il applique des gates objectifs, mutation score, lint, coverage. C’est lui qui transforme un avis en seuil de passage.
Je retiens une conclusion simple. Le writer-agent est remplaçable. Le critic-agent l’est beaucoup moins. C’est lui qui empêche le test theatre, et donc celui dont la qualité conditionne tout le reste.
Trois points à retenir
1. Le test critique doit venir de moi ou d’un outil déterministe. S’il vient du même agent que le code, je perds la contrainte indépendante.
2. Les outils déterministes gardent un terrain utile. EvoSuite, Pynguin, KLEE, GraphWalker, Hypothesis ghostwriter et icontract-hypothesis couvrent des besoins que l’IA traite moins bien, couverture systématique, chemins symboliques, modèles d’état, invariants.
3. Le pattern writer + critic + judge déterministe vaut mieux que l’agent unique. Le critic-agent est souvent l’acteur le plus utile du pipeline.
L’étape suivante est plus simple à formuler et plus difficile à mesurer. Une suite de tests vaut-elle quelque chose ? Couverture, mutation testing, test smells, robustesse. L’article 4 passe en revue les quatre axes que j’utilise pour répondre à cette question.
Pour aller plus loin
- Kent Beck, Augmented Coding: Beyond the Vibes (2025) - signals.aktagon.com
- Mathews et al., Test-Driven Development for Code Generation / TiCoder (arxiv 2402.13521) - arxiv.org
- Tests as Prompt: A TDD Benchmark for LLM Code Generation / TENET (arxiv 2505.09027) - arxiv.org
- Multi-Agent Verification: Scaling Test-Time Compute with Multiple Verifiers (arxiv 2502.20379) - arxiv.org
- Multi-Agent Debate for LLM Judges (arxiv 2510.12697) - arxiv.org
- Pynguin (arxiv 2202.05218) - arxiv.org
- EvoSuite - evosuite.org
- KLEE - klee-se.org
- GraphWalker - graphwalker.github.io
- Hypothesis ghostwriter - hypothesis.readthedocs.io
- icontract-hypothesis - github.com/mristin/icontract-hypothesis
- Le repo public de la série - github.com/mwolff44/spec-to-tests
Cet article est le troisième d’une série de sept. L’article précédent traitait de la spec exécutable. Le prochain parlera de la manière de mesurer si une suite de tests vaut quelque chose.