Que fait Tests de régression pour l'IA ?
Patrons de test spécialement conçus pour le développement assisté par IA, où le même modèle écrit et révise le code — créant des angles morts systématiques que seuls les tests automatisés peuvent détecter.
Quand activer
- Un agent IA (Claude Code, Cursor, Codex) a modifié des routes d'API ou la logique backend
- Un bug a été trouvé et corrigé — besoin d'empêcher sa réintroduction
- Le projet dispose d'un mode bac à sable/mock exploitable pour des tests sans base de données
- Exécution de
/bug-checkou de commandes de révision similaires après des modifications de code - Plusieurs chemins de code existent (bac à sable vs production, feature flags, etc.)
Le problème fondamental
Quand une IA écrit du code puis révise son propre travail, elle transporte les mêmes hypothèses dans les deux étapes. Cela crée un schéma d'échec prévisible :
IA écrit le correctif → IA révise le correctif → IA dit "c'est correct" → Le bug existe toujours
Exemple concret (observé en production) :
Correctif 1 : Ajout de notification_settings à la réponse de l'API
→ Oubli de l'ajouter à la requête SELECT
→ L'IA a révisé et ne l'a pas vu (même angle mort)
Correctif 2 : Ajout à la requête SELECT
→ Erreur de build TypeScript (colonne absente des types générés)
→ L'IA a révisé le correctif 1 mais n'a pas vu le problème SELECT
Correctif 3 : Changement en SELECT *
→ Correction du chemin de production, oubli du chemin bac à sable
→ L'IA a révisé et l'a ENCORE manqué (4ème occurrence)
Correctif 4 : Le test l'a détecté immédiatement au premier passage PASS :
Le schéma : l'incohérence de chemin bac à sable/production est la régression n°1 introduite par l'IA.
Tests d'API en mode bac à sable
La plupart des projets avec une architecture compatible IA disposent d'un mode bac à sable/mock. C'est la clé pour des tests d'API rapides sans base de données.
Configuration (Vitest + Next.js App Router)
// vitest.config.ts
import { defineConfig } from "vitest/config";
import path from "path";
export default defineConfig({
test: {
environment: "node",
globals: true,
include: ["__tests__/**/*.test.ts"],
setupFiles: ["__tests__/setup.ts"],
},
resolve: {
alias: {
"@": path.resolve(__dirname, "."),
},
},
});
// __tests__/setup.ts
// Forcer le mode bac à sable — aucune base de données nécessaire
process.env.SANDBOX_MODE = "true";
process.env.NEXT_PUBLIC_SUPABASE_URL = "";
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = "";
Utilitaire de test pour les routes API Next.js
// __tests__/helpers.ts
import { NextRequest } from "next/server";
export function createTestRequest(
url: string,
options?: {
method?: string;
body?: Record<string, unknown>;
headers?: Record<string, string>;
sandboxUserId?: string;
},
): NextRequest {
const { method = "GET", body, headers = {}, sandboxUserId } = options || {};
const fullUrl = url.startsWith("http") ? url : `http://localhost:3000${url}`;
const reqHeaders: Record<string, string> = { ...headers };
if (sandboxUserId) {
reqHeaders["x-sandbox-user-id"] = sandboxUserId;
}
const init: { method: string; headers: Record<string, string>; body?: string } = {
method,
headers: reqHeaders,
};
if (body) {
init.body = JSON.stringify(body);
reqHeaders["content-type"] = "application/json";
}
return new NextRequest(fullUrl, init);
}
export async function parseResponse(response: Response) {
const json = await response.json();
return { status: response.status, json };
}
Écrire des tests de régression
Le principe clé : écrire des tests pour les bugs qui ont été trouvés, pas pour du code qui fonctionne.
// __tests__/api/user/profile.test.ts
import { describe, it, expect } from "vitest";
import { createTestRequest, parseResponse } from "../../helpers";
import { GET, PATCH } from "@/app/api/user/profile/route";
// Définir le contrat — quels champs DOIVENT être dans la réponse
const REQUIRED_FIELDS = [
"id",
"email",
"full_name",
"phone",
"role",
"created_at",
"avatar_url",
"notification_settings", // ← Ajouté après qu'un bug a révélé son absence
];
describe("GET /api/user/profile", () => {
it("retourne tous les champs obligatoires", async () => {
const req = createTestRequest("/api/user/profile");
const res = await GET(req);
const { status, json } = await parseResponse(res);
expect(status).toBe(200);
for (const field of REQUIRED_FIELDS) {
expect(json.data).toHaveProperty(field);
}
});
// Test de régression — ce bug exact a été introduit par l'IA 4 fois
it("notification_settings n'est pas undefined (régression BUG-R1)", async () => {
const req = createTestRequest("/api/user/profile");
const res = await GET(req);
const { json } = await parseResponse(res);
expect("notification_settings" in json.data).toBe(true);
const ns = json.data.notification_settings;
expect(ns === null || typeof ns === "object").toBe(true);
});
});
Tester la parité bac à sable/production
La régression IA la plus fréquente : corriger le chemin de production mais oublier le chemin bac à sable (ou vice versa).
// Tester que les réponses du bac à sable respectent le contrat attendu
describe("GET /api/user/messages (liste de conversations)", () => {
it("inclut partner_name en mode bac à sable", async () => {
const req = createTestRequest("/api/user/messages", {
sandboxUserId: "user-001",
});
const res = await GET(req);
const { json } = await parseResponse(res);
// Cela a détecté un bug où partner_name avait été ajouté
// au chemin de production mais pas au chemin bac à sable
if (json.data.length > 0) {
for (const conv of json.data) {
expect("partner_name" in conv).toBe(true);
}
}
});
});
Intégration des tests dans le workflow de vérification des bugs
Définition d'une commande personnalisée
<!-- .claude/commands/bug-check.md -->
# Vérification de bugs
## Étape 1 : Tests automatisés (obligatoire, ne peut pas être ignorée)
Exécutez ces commandes EN PREMIER avant toute révision de code :
npm run test # Suite de tests Vitest
npm run build # Vérification des types TypeScript + build
- Si les tests échouent → signaler comme bug de priorité maximale
- Si le build échoue → signaler les erreurs de type comme priorité maximale
- Passez à l'étape 2 uniquement si les deux réussissent
## Étape 2 : Révision de code (révision IA)
1. Cohérence des chemins bac à sable / production
2. Forme de la réponse API conforme aux attentes du frontend
3. Complétude de la clause SELECT
4. Gestion des erreurs avec rollback
5. Conditions de course des mises à jour optimistes
## Étape 3 : Pour chaque bug corrigé, proposer un test de régression
Le workflow
Utilisateur : "バグチェックして" (ou "/bug-check")
│
├─ Étape 1 : npm run test
│ ├─ ÉCHEC → Bug détecté mécaniquement (aucun jugement IA nécessaire)
│ └─ SUCCÈS → Continuer
│
├─ Étape 2 : npm run build
│ ├─ ÉCHEC → Erreur de type détectée mécaniquement
│ └─ SUCCÈS → Continuer
│
├─ Étape 3 : Révision de code IA (en gardant à l'esprit les angles morts connus)
│ └─ Résultats signalés
│
└─ Étape 4 : Pour chaque correctif, écrire un test de régression
└─ La prochaine vérification détecte si le correctif casse
Motifs de régression IA courants
Motif 1 : Incohérence de chemin bac à sable/production
Fréquence : La plus courante (observée dans 3 régressions sur 4)
// ÉCHEC : L'IA ajoute un champ uniquement au chemin de production
if (isSandboxMode()) {
return { data: { id, email, name } }; // Nouveau champ manquant
}
// Chemin de production
return { data: { id, email, name, notification_settings } };
// SUCCÈS : Les deux chemins doivent retourner la même forme
if (isSandboxMode()) {
return { data: { id, email, name, notification_settings: null } };
}
return { data: { id, email, name, notification_settings } };
Test pour le détecter :
it("les modes bac à sable et production retournent les mêmes champs", async () => {
// Dans l'environnement de test, le mode bac à sable est FORCÉ
const res = await GET(createTestRequest("/api/user/profile"));
const { json } = await parseResponse(res);
for (const field of REQUIRED_FIELDS) {
expect(json.data).toHaveProperty(field);
}
});
Motif 2 : Omission dans la clause SELECT
Fréquence : Courant avec Supabase/Prisma lors de l'ajout de nouvelles colonnes
// ÉCHEC : Nouvelle colonne ajoutée à la réponse mais pas au SELECT
const { data } = await supabase
.from("users")
.select("id, email, name") // notification_settings n'est pas ici
.single();
return { data: { ...data, notification_settings: data.notification_settings } };
// → notification_settings est toujours undefined
// SUCCÈS : Utiliser SELECT * ou inclure explicitement les nouvelles colonnes
const { data } = await supabase
.from("users")
.select("*")
.single();
Motif 3 : Fuite d'état d'erreur
Fréquence : Modérée — lors de l'ajout de gestion d'erreurs à des composants existants
// ÉCHEC : État d'erreur défini mais anciennes données non effacées
catch (err) {
setError("Échec du chargement");
// reservations affiche encore les données de l'onglet précédent !
}
// SUCCÈS : Effacer l'état associé en cas d'erreur
catch (err) {
setReservations([]); // Effacer les données obsolètes
setError("Échec du chargement");
}
Motif 4 : Mise à jour optimiste sans rollback approprié
// ÉCHEC : Pas de rollback en cas d'échec
const handleRemove = async (id: string) => {
setItems(prev => prev.filter(i => i.id !== id));
await fetch(`/api/items/${id}`, { method: "DELETE" });
// Si l'API échoue, l'élément a disparu de l'interface mais est toujours en base
};
// SUCCÈS : Capturer l'état précédent et rollback en cas d'échec
const handleRemove = async (id: string) => {
const prevItems = [...items];
setItems(prev => prev.filter(i => i.id !== id));
try {
const res = await fetch(`/api/items/${id}`, { method: "DELETE" });
if (!res.ok) throw new Error("Erreur API");
} catch {
setItems(prevItems); // Rollback
alert("Échec de la suppression");
}
};
Stratégie : tester là où les bugs ont été trouvés
Ne visez pas une couverture à 100 %. Faites plutôt :
Bug trouvé dans /api/user/profile → Écrire un test pour l'API profile
Bug trouvé dans /api/user/messages → Écrire un test pour l'API messages
Bug trouvé dans /api/user/favorites → Écrire un test pour l'API favorites
Aucun bug dans /api/user/notifications → Ne pas écrire de test (pour l'instant)
Pourquoi cela fonctionne avec le développement IA :
- L'IA a tendance à faire la même catégorie d'erreur de manière répétée
- Les bugs se concentrent dans les zones complexes (authentification, logique multi-chemin, gestion d'état)
- Une fois testée, cette régression exacte ne peut pas se reproduire
- Le nombre de tests croît naturellement avec les corrections de bugs — aucun effort gaspillé
Référence rapide
| Motif de régression IA | Stratégie de test | Priorité |
|---|---|---|
| Incohérence bac à sable/production | Affirmer la même forme de réponse en mode bac à sable | Haute |
| Omission clause SELECT | Affirmer que tous les champs obligatoires sont dans la réponse | Haute |
| Fuite d'état d'erreur | Affirmer le nettoyage de l'état en cas d'erreur | Moyenne |
| Rollback manquant | Affirmer que l'état est restauré en cas d'échec API | Moyenne |
| Masquage de null par cast de type | Affirmer que le champ n'est pas undefined | Moyenne |
À FAIRE / À NE PAS FAIRE
À FAIRE :
- Écrire des tests immédiatement après avoir trouvé un bug (avant de le corriger si possible)
- Tester la forme de la réponse API, pas l'implémentation
- Exécuter les tests comme première étape de chaque vérification de bugs
- Garder les tests rapides (< 1 seconde au total avec le mode bac à sable)
- Nommer les tests d'après le bug qu'ils préviennent (ex. "régression BUG-R1")
À NE PAS FAIRE :
- Écrire des tests pour du code qui n'a jamais eu de bug
- Faire confiance à l'auto-révision de l'IA comme substitut aux tests automatisés
- Sauter les tests du chemin bac à sable parce que "ce ne sont que des données mock"
- Écrire des tests d'intégration quand des tests unitaires suffisent
- Viser un pourcentage de couverture — visez la prévention des régressions