« Ça marche » ne veut rien dire pour une IA
J'ai longtemps demandé à l'IA d'écrire une fonction, puis de la « corriger jusqu'à ce que ça marche ». Le problème : ça marche est une notion floue. L'IA croyait avoir fini, je relançais, un cas tombait à l'eau, je re-demandais, un autre cas cassait. On tournait en rond pendant vingt minutes.
Le déclic a été d'inverser l'ordre. Au lieu de décrire vaguement ce que je voulais, j'ai écrit le test d'abord. Le test dit en code, sans ambiguïté : « pour cette entrée, je veux exactement cette sortie ». Soudain, l'IA avait une cible nette. « Terminé » n'était plus une opinion, c'était une barre verte.
Un test, c'est un contrat exécutable. Il transforme « fais quelque chose qui marche » en « fais passer ces assertions ». L'IA adore les cibles mesurables : donnez-lui-en une.
Le test, GPS de l'agent
Un agent IA autonome qui code sans tests, c'est une voiture sans GPS : il avance, mais il ne sait pas s'il se rapproche de la destination. Avec un test, l'agent a un signal clair à chaque tour : rouge (pas encore), vert (objectif atteint).
Mieux : l'agent peut lancer les tests lui-même, lire le message d'erreur, corriger, relancer : en boucle, sans vous. Vous n'écrivez plus le code, vous écrivez la définition de « réussi », et l'IA s'occupe du reste.
- Sans test : l'IA devine si c'est bon, vous vérifiez à la main, vous renvoyez du feedback
- Avec test : l'IA itère seule jusqu'au vert, vous ne validez qu'à la fin
Red, green, refactor : version IA
Le cycle TDD classique a trois temps, et chacun a son rôle quand on pilote une IA :
- Red : vous (ou l'IA) écrivez un test qui échoue. Il décrit le comportement voulu avant qu'il existe
- Green : l'IA écrit le code minimal pour faire passer le test. Rien de plus
- Refactor : une fois vert, on nettoie le code sans changer le comportement. Le test reste vert, c'est le filet de sécurité
Prenons un cas concret : une fonction de calcul de prix avec remise. On écrit d'abord le test, qui décrit exactement les règles métier.
<?php
use PHPUnit\Framework\TestCase;
final class PriceTest extends TestCase
{
public function test_sans_remise(): void
{
$this->assertSame(100.0, calculer_prix(100.0, 0));
}
public function test_remise_20_pourcent(): void
{
$this->assertSame(80.0, calculer_prix(100.0, 20));
}
public function test_remise_negative_interdite(): void
{
$this->expectException(InvalidArgumentException::class);
calculer_prix(100.0, -5);
}
}
Ce test est le cahier des charges. L'IA n'a plus à deviner : une remise de 0 ne change rien, 20 % retire un cinquième, et une remise négative doit lever une exception. On demande alors le code green.
<?php
function calculer_prix(float $base, float $remise): float
{
if ($remise < 0 || $remise > 100) {
throw new InvalidArgumentException('Remise invalide');
}
return round($base * (1 - $remise / 100), 2);
}
Le test empêche l'IA de casser l'existant
Le danger numéro un en codant avec l'IA : elle « améliore » une fonction et casse trois autres choses sans s'en rendre compte. C'est là que la suite de tests devient inestimable.
Quand l'IA modifie du code couvert par des tests, vous relancez la suite. Si un test au vert passe au rouge, vous savez immédiatement ce qui a régressé : avant même de déployer. C'est ce qu'on appelle un test de non-régression.
# Avant de faire confiance à une modif de l'IA
./vendor/bin/phpunit
# Sortie attendue
OK (3 tests, 3 assertions)
# Si l'IA a cassé quelque chose
FAILURES! Tests: 3, Failures: 1.
# -> on sait exactement quoi corriger avant de déployer
Ne laissez jamais l'IA écrire à la fois le code et ses tests sans les relire. Une IA peut générer un test bidon qui passe toujours (par exemple assertTrue(true)). Un test vert ne vaut que si vous avez vérifié qu'il teste vraiment le bon comportement.
"It works" means nothing to an AI
For a long time I asked the AI to write a function, then to "fix it until it works". The problem: it works is a fuzzy notion. The AI thought it was done, I reran it, one case failed, I asked again, another case broke. We went in circles for twenty minutes.
The breakthrough was reversing the order. Instead of vaguely describing what I wanted, I wrote the test first. The test states, in code, without ambiguity: "for this input, I want exactly this output". Suddenly the AI had a sharp target. "Done" was no longer an opinion, it was a green bar.
A test is an executable contract. It turns "make something that works" into "make these assertions pass". AI loves measurable targets — give it one.
The test, the agent's GPS
An autonomous AI agent coding without tests is a car without GPS: it moves, but it does not know whether it is getting closer to the destination. With a test, the agent has a clear signal each turn: red (not yet), green (goal reached).
Better: the agent can run the tests itself, read the error message, fix, rerun — in a loop, without you. You no longer write the code, you write the definition of "passing", and the AI handles the rest.
- Without tests: the AI guesses if it is good, you check by hand, you send feedback
- With tests: the AI iterates on its own until green, you only validate at the end
Red, green, refactor — the AI version
The classic TDD cycle has three steps, and each has its role when driving an AI:
- Red — you (or the AI) write a failing test. It describes the wanted behavior before it exists
- Green — the AI writes the minimal code to pass the test. Nothing more
- Refactor — once green, clean the code without changing behavior. The test stays green, it is the safety net
Take a concrete case: a price calculation with a discount. We write the test first, describing the exact business rules.
<?php
use PHPUnit\Framework\TestCase;
final class PriceTest extends TestCase
{
public function test_no_discount(): void
{
$this->assertSame(100.0, calculer_prix(100.0, 0));
}
public function test_discount_20_percent(): void
{
$this->assertSame(80.0, calculer_prix(100.0, 20));
}
public function test_negative_discount_forbidden(): void
{
$this->expectException(InvalidArgumentException::class);
calculer_prix(100.0, -5);
}
}
This test is the specification. The AI no longer has to guess: a 0 discount changes nothing, 20% removes a fifth, and a negative discount must throw an exception. We then ask for the green code.
<?php
function calculer_prix(float $base, float $remise): float
{
if ($remise < 0 || $remise > 100) {
throw new InvalidArgumentException('Invalid discount');
}
return round($base * (1 - $remise / 100), 2);
}
Tests stop the AI from breaking existing code
The number one danger when coding with AI: it "improves" a function and breaks three other things without noticing. This is where the test suite becomes invaluable.
When the AI modifies code covered by tests, you rerun the suite. If a green test turns red, you know immediately what regressed — before even deploying. This is called a regression test.
# Before trusting an AI change
./vendor/bin/phpunit
# Expected output
OK (3 tests, 3 assertions)
# If the AI broke something
FAILURES! Tests: 3, Failures: 1.
# -> you know exactly what to fix before deploying
Never let the AI write both the code and its tests without reviewing them. An AI can generate a bogus test that always passes (e.g. assertTrue(true)). A green test only counts if you verified it actually tests the right behavior.
Demandez à l'IA d'écrire les tests AVANT le code, puis de coder jusqu'au vert :
On va faire du TDD. Étape 1 : écris uniquement les tests PHPUnit pour une fonction calculer_prix(base, remise) qui applique une remise en pourcentage et lève une exception si la remise est hors de [0, 100]. Ne génère pas encore la fonction. Étape 2, quand je dis OK : écris le code minimal pour faire passer ces tests.
Sans relire la leçon : pourquoi écrire le test AVANT de demander le code à l'IA change-t-il tout ? Qu'est-ce que ça donne à l'agent qu'un simple « fais-moi une fonction qui marche » ne donne pas ?
Tu as demandé à l'IA le test AVANT le code, pour la fonction calculer_prix(base, remise). Elle te renvoie ce test. Ton rôle de relecteur : l'accepter tel quel ou le rejeter, et dire pourquoi.
public function test_calculer_prix(): void
{
$resultat = calculer_prix(100.0, 20);
$this->assertNotNull($resultat);
$this->assertTrue(is_float($resultat));
}
calculer_prix(100.0, 20) vaut bien 80.0. C'est exactement le piège du test bidon évoqué plus haut : une barre verte qui ne prouve aucune règle métier. Le vrai test doit asserter la valeur exacte (assertSame(80.0, …)) et couvrir les cas limites (remise à 0, remise négative qui lève une exception). Un test qui ne peut pas échouer ne sert à rien comme contrat pour l'IA.Objectif : une fonction est_email_valide(string $email): bool. Écrivez au moins deux cas de test (un valide, un invalide) avec assert avant d'écrire la fonction. Pensez aussi au cas limite (chaîne vide).
Sans remonter dans la leçon : nomme les trois temps du cycle red-green-refactor et dis en une phrase ce que fait chacun quand on pilote une IA.
Les tests valident que le code marche, pas qu'il est bien écrit ni sûr. La prochaine leçon, la code review assistée, transforme l'IA en relecteur : lui faire critiquer son propre code (lisibilité, cas oubliés, mauvaises pratiques) avant de le garder.
Leçon 7 : Code review assistée →