test-driven-development — red, green, refactor
La règle : aucun code production sans un test défaillant d'abord. Un test écrit après le code réussit immédiatement — et un test que tu n'as jamais vu échouer ne prouve rien : il peut tester la mauvaise chose, tester l'implémentation au lieu du comportement, ou rater le cas exact que tu as raté.
Tu as déjà écrit du code avant le test ? Remise ou suppression, écris le test, regarde-le échouer, puis réimplémente. Ne le laisse pas ouvert « comme référence » — c'est faire des tests après, avec des étapes supplémentaires.
Le cycle
1. RED — écrire un test défaillant
Un comportement par test, nommé pour le comportement ("rejects a receipt line with zero quantity", pas "test1"). Teste du vrai code ; mock seulement ce que tu ne peux pas exécuter (réseau, horloge). Copie la configuration d'un fichier test frère dans le même package.
Où vivent les tests : packages/{pkg}/src/**/*.test.ts ou apps/erp/app/modules/{module}/__tests__/ (suis les voisins). Le runner est vitest via le preset @carbon/config.
2. Vérifier RED — le voir échouer (obligatoire, ne saute jamais)
pnpm --filter <pkg> exec vitest run <path/to/file.test.ts>
# Attendu : FAIL — assertion failure pour le comportement manquant
- Échoue avec une assertion sur le comportement manquant → continue.
- Réussit → il teste un comportement existant ; réécris le test.
- Erreurs (import/type/syntax) → corrige l'erreur, relance jusqu'à ce qu'il échoue correctement.
- Piège Carbon : vitest s'exécute avec
passWithNoTests: true— une exécution verte qui a collecté 0 tests ne prouve rien. Vérifie le nombre de tests en sortie.
3. GREEN — code minimal pour passer
Écris le code le plus simple qui fait passer le test. Aucune option supplémentaire, aucun paramètre spéculatif, aucune refonte du code voisin, rien que le test ne demande.
pnpm --filter <pkg> exec vitest run <path/to/file.test.ts> # ton test passe
pnpm --filter <pkg> test # le package passe toujours
Le test échoue → corrige le code, pas le test. D'autres tests échouent → corrige maintenant, pas plus tard.
4. REFACTOR — uniquement en vert
Enlève la duplication, améliore les noms, extrais des helpers. Aucun nouveau comportement. Relance les tests du package après. Puis boucle au prochain test défaillant.
Après avoir modifié les tests, typecheck aussi le package — pnpm exec turbo run typecheck --filter=<pkg> (jamais le repo entier). Le repo utilise noUncheckedIndexedAccess : l'accès indexé dans les assertions a besoin du chaînage optionnel (rows[0]?.id), et ça ne remonte que au typecheck, pas à la run du test.
Corrections de bugs
Une correction de bug est du TDD avec le test écrit depuis le rapport de bug : le test défaillant reproduit le bug, la correction le rend vert, et le test reste comme garde de régression. Ne corrige jamais un bug sans un (voir /fix, qui force ça).
Rationalisations — elles signifient toutes « écris le test d'abord »
| Excuse | Réalité |
|---|---|
| « Trop simple à tester » | Le code simple casse ; le test coûte 30 secondes |
| « Je testerai après » | Qui réussit immédiatement ne prouve rien |
| « Je l'ai testé manuellement » | Non reproductible, aucun enregistrement, parti au prochain changement |
| « Supprimer X heures de travail gaspille » | Coût irrécupérable ; du code non vérifié c'est la vraie dette |
| « Juste cette fois / c'est différent » | L'exception devient l'habitude |
| « Le test est difficile à écrire » | Difficile à tester = difficile à utiliser ; simplifie l'interface |
Fait quand
- [ ] Chaque nouveau comportement a un test que tu as vu échouer, pour la bonne raison
- [ ]
pnpm --filter <pkg> testvert, avec un nombre de tests collectés non nul - [ ]
pnpm exec turbo run typecheck --filter=<pkg>vert - [ ] Sortie impeccable — aucun avertissement/erreur stray que tu as introduits
En ajoutant des mocks ou des utilitaires de test, lis d'abord testing-anti-patterns.md — ça couvre tester le comportement du mock au lieu du comportement réel, les méthodes test-only sur les classes production, et mocker sans comprendre la dépendance.