← Retour aux articles
11 November 2026 9 min de lecture Claude CodeIATestsSpec KitPalierMaintenant

Les tests passent. Ça ne dit pas que ça marche.

Comment je valide du code écrit par une IA : un harnais qui l'empêche de tricher, puis une machine virtuelle jetable qui lui oppose le réel.

Je développe mes produits avec un agent de code. Il écrit, je cadre, je relis, je décide. Cette façon de travailler pose une question que tout le monde finit par se poser : comment savoir que ce qui a été produit fait vraiment ce qui était demandé ?

Ma réponse tient en deux étages. Le premier s’appelle speckit-guard. Le second est un banc de test dans une machine virtuelle. Cet article explique pourquoi le premier ne suffit pas, et ce que le second apporte.

Premier étage : empêcher l’agent d’être juge de son travail

Un agent qui implémente une fonctionnalité écrit aussi ses tests, et décide lui-même quand c’est fini. Quand un test échoue, le chemin le plus court vers le vert consiste souvent à modifier le test plutôt que le code.

speckit-guard est le plugin Claude Code que j’ai écrit pour fermer cette porte, par-dessus GitHub Spec Kit. Il fait trois choses :

  1. Les tests d’acceptation sont écrits à partir de la spec, avant qu’une ligne de code existe, par un sous-agent isolé qui ne lit que la spec.
  2. Ces tests sont verrouillés pendant l’implémentation. Un hook bloque toute tentative de les modifier : l’agent doit faire évoluer le code, pas la cible.
  3. À la fin, un relecteur en contexte vierge, qui n’a jamais vu le raisonnement de l’implémenteur, juge chaque critère d’acceptation : OK, partiel ou manquant.

Avec ça, « tous les tests passent » veut enfin dire quelque chose : le code respecte la spec.

Ce que ce harnais ne peut pas voir

Il prouve que le code respecte la spec. Il ne prouve pas que la spec survit au contact d’une vraie machine.

Mes produits agissent sur des serveurs. Palier met à jour des stacks Docker Compose en lisant les notes de version. Maintenant (lauréat AAP Hyper Open X France 2030) surveille des conteneurs et fait remonter des alertes. Leur terrain de jeu n’est pas une fonction qu’on appelle avec des entrées propres. C’est un moteur Docker, un registre d’images qui répond ou pas, une base PostgreSQL avec des données dedans, un GitLab qui refuse de sauter une version, un conteneur qui meurt d’un signal plutôt que d’un autre.

Un test d’acceptation peut simuler tout ça. Mais une simulation contient ce que son auteur imagine du réel, et l’auteur ici est une IA qui a lu la même spec que celle qui a écrit le code. Les deux peuvent se tromper ensemble, de la même façon, et tout reste vert.

Deux exemples tirés de mes bancs. Sur Maintenant, un conteneur tué avec un code de sortie 0, 137 ou 143 est considéré comme terminé, pas comme tombé : il ne déclenche pas d’alerte. Pour tester une vraie panne, il faut un vrai crash. Sur Palier, l’image de GitLab porte une étiquette de version « 22.04 », héritée de sa base Ubuntu, qui contredit son tag. Ce genre de détail n’existe dans aucune spec. Il existe dans le réel.

Et voici un cas réel, sur Palier, le 6 novembre 2026.

Palier ne retient un correctif de sécurité dans son plan que si la phrase qui le justifie figure mot pour mot dans les notes de version. C’est le garde-fou qui empêche l’IA d’affirmer quelque chose sans preuve. Un test unitaire couvrait cette règle. Il était vert.

Sur le banc, avec les vrais avis de sécurité GitHub d’authentik, le plan sortait incomplet : le correctif de sécurité était écarté, sa citation étant déclarée introuvable. Elle y était pourtant. Les 30 avis utilisent des fins de ligne Windows (\r\n), et l’IA ne les recopie pas quand elle cite. La comparaison stricte échouait sur un caractère invisible.

Le test unitaire, lui, tournait sur des textes écrits pour l’occasion, avec des fins de ligne Unix. Il vérifiait la règle sur des données imaginées. Le banc l’a vérifiée sur des données que personne n’avait écrites pour le test.

La correction tient en deux points : les notes sont ramenées à un seul type de fin de ligne dès leur nettoyage, et la citation est comparée sans tenir compte des espaces. Un test avec \r\n a été ajouté, puis le scénario a été rejoué : plan complet, en 6 paliers.

Sans le banc, ce bug partait chez les utilisateurs. Et il aurait frappé précisément les mises à jour de sécurité, celles qu’on veut voir passer en premier.

Tester à la main ne tient pas

La réponse classique : je lance, je regarde. Elle ne tient pas pour trois raisons.

Le temps. Faire monter un GitLab de la 16.1.6 à la 19.5.1 en respectant ses arrêts obligatoires prend 13 étapes et 3 h 43. Une chaîne d’escalade à trois niveaux, c’est plusieurs minutes d’attente devant une horloge à chaque essai.

La combinatoire. Une mise à jour peut échouer à l’étape 2, à l’étape 3, pendant la sauvegarde, pendant la vérification, pendant le retour arrière lui-même. Multiplié par le nombre d’applications et de bases de données, personne ne rejoue tout ça à la main après chaque changement. On teste le cas nominal, et on espère pour le reste.

La machine de dev. Tester Palier sur mon poste mélange ses conteneurs avec les miens, et Palier me propose alors de mettre à jour mes propres outils. Un produit qui agit sur un serveur ne se teste pas sur la machine qui sert à le développer.

Or le problème grossit avec l’IA. L’agent produit plus de code, plus vite, que je ne peux en éprouver à la main. Si la vérification reste manuelle, c’est elle qui devient le goulot, ou bien on arrête de vérifier.

Second étage : une VM jetable, de vraies applications, des scénarios rejouables

Le banc de Palier est une machine virtuelle libvirt, créée et provisionnée par Ansible à partir d’une image Debian. Une commande la monte, une autre la détruit. Rien ne tourne sur le Docker de ma machine.

Dedans, il y a Palier, construit depuis ma copie de travail, et ce contre quoi il doit être éprouvé :

  • de vraies applications, volontairement installées dans de vieilles versions : nginx, whoami, Outline avec son PostgreSQL et son Redis, GitLab CE, Immich, Vaultwarden, MariaDB, MySQL ;
  • des données de départ : trois documents dans Outline, un projet et un compte dans GitLab, des photos dans Immich ;
  • des capteurs pour tout ce que le produit envoie vers l’extérieur : un récepteur de webhooks, une boîte mail de test, un fournisseur d’identité de test, un registre privé avec son TLS, des destinations de sauvegarde S3, SFTP et WebDAV.

Chaque version de départ est choisie pour une raison, et la raison est écrite dans le dépôt. GitLab 16.1.6 parce que le chemin vers la version courante impose une série d’arrêts obligatoires. Immich v1.105.1 parce que c’est la dernière version avant deux séries de changements cassants dans son fichier Compose.

Par-dessus, près de quarante scénarios rejouables. Les cas nominaux : appliquer tout le plan d’Outline et recompter les documents, monter tout le chemin de GitLab et vérifier que le projet est toujours là. Et surtout les pannes provoquées :

  • l’image cible est remplacée par une image locale qui s’arrête dès son démarrage ;
  • Palier est tué pendant qu’il vérifie une étape, puis relancé ;
  • l’export d’une base est coupé en plein envoi vers le stockage ;
  • le tag visé disparaît du registre après le calcul du plan ;
  • le retour arrière lui-même est rendu impossible ;
  • les notes de version sont piégées, pour vérifier qu’aucune instruction cachée ne finit dans un plan appliqué.

Le banc de Maintenant suit la même logique pour l’escalade des alertes : une machine QEMU jetable, trois applications, dont une qu’on fait planter. Le scénario vérifie qu’une alerte non acquittée grimpe les trois niveaux, à 60, 120 puis 180 secondes, jusqu’au bout de la chaîne. Montage en 3 minutes environ, exécution en 7, sans personne devant.

Les cinq règles de mes bancs

1. Isolé et jetable. Tout se passe dans la machine. On la détruit, on la remonte, on repart du même état.

2. Du vrai, pas du simulé. Un vrai moteur Docker, de vraies images, de vraies bases avec des données. Le but du banc est justement de sortir de ce que le code imagine.

3. Le verdict vient de l’extérieur du produit. Sur Maintenant, le verdict est calculé à partir de ce que la boîte mail et le récepteur de webhooks ont réellement reçu, pas de ce que le produit dit avoir envoyé. Sur Palier, on recompte les documents et on compare l’empreinte des photos avant et après. Un produit qui se déclare lui-même en bonne santé ne prouve rien.

4. On teste aussi ce qui ne doit pas arriver. Dans le scénario d’escalade, une deuxième application reçoit un signal qui la fait simplement recharger sa configuration. Elle doit rester en service, sans alerte. Un banc qui ne vérifie que les alertes attendues laisse passer les fausses alertes.

5. Une commande, un code de sortie. Chaque scénario se lance d’une ligne et rend un verdict lisible par une machine. C’est ce qui le rend rejouable après chaque changement, par moi ou par l’agent.

Une précision : tout ne mérite pas une VM. Pour tester l’expiration de session de Maintenant derrière un proxy d’authentification, un simple fichier Compose suffit. La VM se justifie quand le produit touche à l’hôte lui-même : son moteur Docker, ses fichiers, son réseau.

Ce que ça coûte

Le banc est un vrai projet dans le projet. Il faut un hôte Linux avec KVM, plusieurs gigaoctets de mémoire (4 pour le petit jeu d’applications de Palier, 10 pour le grand), des dizaines de gigaoctets de disque. Le premier montage est long. Et le banc est du code : il se maintient, il se relit, il peut avoir ses propres bugs.

Je le paie volontiers, parce que le coût est fixe et que le bénéfice se répète. Un scénario écrit une fois se rejoue à chaque modification du code, pendant que je fais autre chose.

Ce que ça change

Sur le dépôt de Palier, la page d’accueil ne dit pas « testé ». Elle dit ce qui s’est passé sur le banc : GitLab monté de la 16.1.6 à la 19.5.1 en 13 étapes, Immich passé de la v1.105.1 à la v3.3.5 avec deux changements cassants absorbés, les photos téléchargées ensuite avec la même empreinte qu’avant, et un échec forcé à la deuxième étape qui rend les fichiers et les volumes à l’identique.

C’est la différence entre affirmer et montrer. Quand le code est écrit par une IA, cette différence compte double : je ne demande à personne de croire l’agent, ni de me croire moi. Je montre ce que la machine a constaté.

Le harnais répond à la question « le code fait-il ce que dit la spec ? ». Le banc répond à la question « est-ce que ça marche sur un serveur ? ». Avec un agent de code, il faut les deux, et il faut que les deux tournent sans moi.


Sources

Besoin d'aide sur ce sujet ?

Réserver un créneau