Pilier 1 : tester son code IA avant de le shipper (TDD, unit tests, e2e)
L'IA génère du code qui marche dans le prompt. Pas forcément en production. Voici comment tester du code que vous n'avez pas écrit : TDD, tests unitaires, intégration, e2e.
Le code qui “marche” mais qui ne marche pas
La semaine dernière, j’ai demandé à Claude de me générer une fonction de parsing pour des webhooks Stripe. Le code était propre. Bien structuré. Les types TypeScript étaient corrects. J’ai lu le code, j’ai hoché la tête, j’ai mergé.
Le lendemain, premier paiement réel. Crash.
La fonction gérait parfaitement le happy path : un événement checkout.session.completed bien formé, avec tous les champs remplis. Mais le premier utilisateur qui a déclenché un invoice.payment_failed avec un champ customer null a fait exploser toute la chaîne.
Le code marchait. Dans le prompt. Pas en production.
C’est le piège fondamental du vibe coding. L’IA optimise pour répondre à votre demande. Pas pour survivre au monde réel. Elle génère du code qui satisfait la question posée, pas les questions que vous n’avez pas posées.
Et c’est exactement pour ça que les tests existent.
Pourquoi Robert C. Martin insiste sur les tests
Dans The Clean Coder, Robert C. Martin pose une règle simple : un développeur professionnel ne livre pas du code sans tests. Point.
Pas parce que c’est joli. Pas parce que c’est une best practice à la mode. Parce que c’est la seule façon de savoir que le code fonctionne.
Il définit les 3 lois du TDD (Test-Driven Development) :
- Tu n’écriras pas de code de production sans d’abord écrire un test qui échoue.
- Tu n’écriras pas plus de test qu’il n’en faut pour échouer.
- Tu n’écriras pas plus de code qu’il n’en faut pour faire passer le test.
Le cycle rouge-vert-refactor. Écrire un test qui échoue. Écrire le minimum de code pour le faire passer. Nettoyer. Recommencer.
Quand vous écrivez du code vous-même, vous avez un modèle mental. Vous savez quels edge cases existent parce que vous avez réfléchi au problème. Vous savez que null peut arriver parce que vous connaissez l’API en amont.
Quand l’IA écrit le code, vous n’avez rien de tout ça. Vous avez du code qui a l’air correct. Mais vous n’avez pas le modèle mental qui va avec.
Les tests compensent ce manque. Ils sont le filet de sécurité entre le code que vous n’avez pas écrit et la production où il va tourner.
Les 3 types de tests
Tous les tests ne se valent pas. Chacun attrape un type de bug différent. En tant que builder solo qui utilise l’IA, vous avez besoin des trois.
Tests unitaires
Le test unitaire isole une fonction et vérifie qu’elle fait ce qu’elle prétend faire. C’est le test le plus rapide, le moins cher, et celui que l’IA écrit le mieux.
// Vitest
import { describe, it, expect } from 'vitest'
import { parseWebhookEvent } from './stripe'
describe('parseWebhookEvent', () => {
it('should extract customer email from checkout event', () => {
const event = mockCheckoutEvent({ email: 'user@test.com' })
expect(parseWebhookEvent(event).email).toBe('user@test.com')
})
it('should throw on missing customer field', () => {
const event = mockCheckoutEvent({ customer: null })
expect(() => parseWebhookEvent(event)).toThrow('Missing customer')
})
it('should handle empty string email', () => {
const event = mockCheckoutEvent({ email: '' })
expect(parseWebhookEvent(event).email).toBeNull()
})
})
Vitest ou Jest pour JavaScript/TypeScript. pytest pour Python. Go a son framework de test intégré. Peu importe l’outil : ce qui compte, c’est de tester les cas limites.
Le happy path, l’IA le gère. Ce sont les null, les strings vides, les tableaux de 10 000 éléments, les caractères Unicode qui font planter les choses.
Les tests unitaires attrapent les erreurs de logique. Ils tournent en millisecondes. Vous pouvez en avoir 500 et les exécuter en 2 secondes.
Tests d’intégration
Le test unitaire dit : “cette fonction marche seule.” Le test d’intégration dit : “ces fonctions marchent ensemble.”
C’est ici que les vrais bugs vivent. La fonction A retourne une string. La fonction B attend un number. Chaque fonction passe ses tests unitaires. Ensemble, ça explose.
describe('Checkout flow', () => {
it('should create order from Stripe webhook', async () => {
const webhook = buildStripeWebhook('checkout.session.completed')
const response = await app.inject({
method: 'POST',
url: '/webhooks/stripe',
payload: webhook,
headers: stripeHeaders(webhook),
})
expect(response.statusCode).toBe(200)
const order = await db.orders.findFirst({
where: { stripeSessionId: webhook.data.object.id },
})
expect(order).not.toBeNull()
expect(order.status).toBe('completed')
})
})
Les tests d’intégration sont plus lents. Ils ont besoin d’une base de données de test, d’un serveur qui tourne. Mais ils attrapent les bugs que les tests unitaires ne voient pas : les problèmes de sérialisation, les erreurs de connexion, les race conditions.
Quand l’IA génère plusieurs modules qui doivent communiquer, les tests d’intégration sont votre seule garantie que le tout fonctionne.
Tests e2e
Le test end-to-end simule un vrai utilisateur. Il ouvre un navigateur, clique sur des boutons, remplit des formulaires, et vérifie que le résultat est correct.
// Playwright
import { test, expect } from '@playwright/test'
test('user can complete checkout', async ({ page }) => {
await page.goto('/products')
await page.click('[data-testid="add-to-cart"]')
await page.click('[data-testid="checkout"]')
await page.fill('#email', 'test@example.com')
await page.fill('#card', '4242424242424242')
await page.click('#pay')
await expect(page.locator('.confirmation')).toContainText('Merci')
})
Playwright ou Cypress. Les tests e2e sont lents, de 10 secondes à plusieurs minutes par test. Mais ils attrapent ce que les humains vivent réellement. Le bouton qui ne répond pas. Le redirect qui boucle. Le formulaire qui perd les données après un refresh.
Pour un builder solo, 5 à 10 tests e2e sur les parcours critiques suffisent. Login → action principale → résultat attendu. Le chemin que vos utilisateurs empruntent tous les jours.
Comment faire écrire les tests PAR l’IA
L’ironie du truc : vous pouvez utiliser l’IA pour tester le code de l’IA. Et ça marche. Si vous savez comment lui demander.
Prompt naïf : “Écris des tests pour cette fonction.”
Résultat : l’IA écrit des tests qui passent. Toujours. Parce qu’elle teste le happy path qu’elle a elle-même codé. C’est comme demander à un étudiant de corriger sa propre copie.
Prompt efficace : “Écris des tests qui CASSENT cette fonction. Trouve les edge cases. Que se passe-t-il avec null ? Avec une string vide ? Avec un tableau de 100 000 éléments ? Avec des caractères Unicode ? Avec une connexion qui timeout ?”
Là, l’IA devient redoutable. Elle connaît les patterns de bugs. Elle sait que JSON.parse peut throw. Elle sait que Array.prototype.find retourne undefined. Il faut juste lui dire de chercher les failles au lieu de confirmer que tout va bien.
Ma stratégie en 3 étapes :
- J’écris les specs en langage naturel. “Cette fonction doit parser un webhook Stripe, extraire l’email du customer, et retourner null si le champ est manquant.”
- Je demande à l’IA de générer l’implémentation.
- Je demande à l’IA de générer les tests adversaires, ceux qui essaient de casser l’implémentation.
Si vous utilisez Claude Code comme copilote, vous pouvez aller plus loin. Décrivez vos conventions de test dans votre CLAUDE.md. Listez les edge cases récurrents de votre projet. L’IA s’en souviendra à chaque génération.
Et avec les hooks de Claude Code, vous pouvez même exécuter les tests automatiquement à chaque commit. L’IA génère le code, les hooks lancent les tests, vous ne mergez que si c’est vert.
La règle du “pas de merge sans vert”
Le meilleur test du monde ne sert à rien si personne ne le lance.
La règle est simple : aucun code ne passe en production sans que tous les tests soient verts. Pas “la plupart”. Pas “les importants”. Tous.
Concrètement :
- Pre-commit hooks qui lancent les tests unitaires avant chaque commit. Si un test échoue, le commit est bloqué.
husky+lint-stageden JS,pre-commiten Python. - CI pipeline qui lance les tests d’intégration et e2e sur chaque push. GitHub Actions, GitLab CI, peu importe. Si c’est rouge, le merge est bloqué.
- Zéro exception. Le jour où vous dites “je skip les tests juste cette fois”, c’est le jour où le bug part en prod.
Les tests sont le premier pilier. Mais les lancer manuellement, ça ne tient pas. C’est là que l’automatisation entre en jeu : le CI/CD, le monitoring, les alertes qui vous réveillent avant vos utilisateurs.
C’est le sujet du prochain article : CI/CD et monitoring pour builder solo.
En résumé
| Type de test | Ce qu’il attrape | Outil | Temps de setup |
|---|---|---|---|
| Unitaire | Erreurs de logique, edge cases | Vitest, Jest, pytest | 10 minutes |
| Intégration | Problèmes entre modules, sérialisation, DB | Supertest, pytest + fixtures | 30 minutes |
| E2E | Bugs UX, parcours cassés, régressions visuelles | Playwright, Cypress | 1-2 heures |
Le code de l’IA n’est pas magique. C’est du code. Et tout code a besoin de tests.
La différence avec le code que vous écrivez vous-même, c’est que vous n’avez pas le modèle mental qui va avec. Vous ne savez pas pourquoi l’IA a fait tel choix. Vous ne savez pas quels raccourcis elle a pris.
Les tests comblent ce trou. Ils transforment “ça a l’air de marcher” en “ça marche, et voici la preuve.”
C’est le premier pilier du manifeste pour shipper du code IA propre. Les deux autres suivent. Mais sans celui-ci, rien ne tient.
Pierre Rondeau
Développeur et indie builder. Je construis des produits et automatisations avec l'IA. Créateur de Claude Hub.
LinkedIn