Développement Piloté par les Tests (TDD)
Philosophie
Principe fondamental : Les tests doivent vérifier le comportement à travers les interfaces publiques, pas les détails d'implémentation. Le code peut changer entièrement ; les tests ne devraient pas.
Les bons tests sont de type intégration : ils exercent les chemins de code réels à travers les API publiques. Ils décrivent ce que le système fait, pas comment il le fait. Un bon test se lit comme une spécification – « l'utilisateur peut finaliser sa commande avec un panier valide » vous dit exactement quelle capacité existe. Ces tests survivent aux refontes car ils ne se soucient pas de la structure interne.
Les mauvais tests sont couplés à l'implémentation. Ils simulent (mock) des collaborateurs internes, testent des méthodes privées, ou vérifient via des moyens externes (comme interroger une base de données directement au lieu d'utiliser l'interface). Le signe d'alerte : votre test se casse lors d'une refonte, alors que le comportement n'a pas changé. Si vous renommez une fonction interne et que les tests échouent, ces tests testaient l'implémentation, pas le comportement.
Voir tests.md pour des exemples et mocking.md pour les directives de simulation.
Anti-patron : Tranches Horizontales
N'écrivez PAS tous les tests d'abord, puis toute l'implémentation. C'est la « tranche horizontale » – traiter ROUGE comme « écrire tous les tests » et VERT comme « écrire tout le code ».
Cela produit des tests médiocres :
- Les tests écrits en masse testent un comportement imaginé, pas un comportement réel
- Vous finissez par tester la forme des choses (structures de données, signatures de fonctions) plutôt que le comportement utilisateur
- Les tests deviennent insensibles aux changements réels – ils passent quand le comportement se casse, échouent quand le comportement est bon
- Vous dépassez vos limites, vous vous engagez dans une structure de test avant de comprendre l'implémentation
Approche correcte : Tranches verticales via des balles traçantes (tracer bullets). Un test → une implémentation → répéter. Chaque test répond à ce que vous avez appris du cycle précédent. Puisque vous venez d'écrire le code, vous savez exactement quel comportement compte et comment le vérifier.
MAUVAIS (horizontal) :
ROUGE : test1, test2, test3, test4, test5
VERT : impl1, impl2, impl3, impl4, impl5
BON (vertical) :
ROUGE→VERT : test1→impl1
ROUGE→VERT : test2→impl2
ROUGE→VERT : test3→impl3
...
Workflow
1. Planification
En explorant la base de code, utilisez le glossaire du domaine du projet pour que les noms de test et le vocabulaire de l'interface correspondent au langage du projet, et respectez les ADR (Architecture Decision Records) dans la zone que vous touchez.
Avant d'écrire du code :
- Confirmer avec l'utilisateur quels changements d'interface sont nécessaires
- Confirmer avec l'utilisateur quels comportements tester (prioriser)
- Identifier les opportunités de modules profonds (interface petite, implémentation profonde)
- Concevoir les interfaces pour la testabilité
- Lister les comportements à tester (pas les étapes d'implémentation)
- Obtenir l'approbation de l'utilisateur sur le plan
Demandez : « À quoi devrait ressembler l'interface publique ? Quels comportements sont les plus importants à tester ? »
On ne peut pas tout tester. Confirmez avec l'utilisateur exactement quels comportements comptent le plus. Concentrez l'effort de test sur les chemins critiques et la logique complexe, pas sur chaque cas limite possible.
2. Balle traçante
Écrivez UN test qui confirme UNE chose sur le système :
ROUGE : Écrire le test pour le premier comportement → le test échoue
VERT : Écrire le code minimal pour passer → le test réussit
C'est votre balle traçante – prouve que le chemin fonctionne de bout en bout.
3. Boucle incrémentale
Pour chaque comportement restant :
ROUGE : Écrire le test suivant → échoue
VERT : Code minimal pour passer → réussit
Règles :
- Un test à la fois
- Seulement assez de code pour passer le test actuel
- N'anticipez pas les tests futurs
- Gardez les tests centrés sur le comportement observable
4. Refactoring
Après que tous les tests réussissent, recherchez des candidats au refactoring :
- Extraire les duplications
- Approfondir les modules (déplacer la complexité derrière des interfaces simples)
- Appliquer les principes SOLID là où c'est naturel
- Considérer ce que le nouveau code révèle sur le code existant
- Exécuter les tests après chaque étape de refactoring
Ne refactorisez jamais en ROUGE. Passez d'abord au VERT.
Checklist par cycle
[ ] Le test décrit le comportement, pas l'implémentation
[ ] Le test utilise uniquement l'interface publique
[ ] Le test survivrait à une refonte interne
[ ] Le code est minimal pour ce test
[ ] Aucune fonctionnalité spéculative ajoutée