Chaque fois que j'arrive sur une nouvelle mission, la question revient. Parfois formulée avec bienveillance, parfois avec scepticisme : "Tu fais vraiment du TDD strict ? Même sur les features compliquées ?" La réponse est oui — et pas par dogmatisme. Après 15 ans sur des projets .NET, c'est la pratique qui m'a le plus protégé, moi et mes clients.
Ce que "strict" veut vraiment dire
Le TDD existe en deux saveurs. Le TDD "souple" : on écrit parfois des tests avant, parfois après, selon le temps disponible. Le TDD strict : pas une seule ligne de code de production sans un test rouge qui la justifie. Point final.
La règle que j'applique dans tous mes projets : Code-before-tests = delete. Si du code de production existe sans test correspondant, il est supprimé. Ce n'est pas une punition — c'est une discipline structurelle qui évite toutes les exceptions, toutes les négociations de couloir, tous les "juste cette fois-ci".
"Le TDD n'est pas une pratique de test. C'est une pratique de design. Les tests sont le sous-produit — le bon design est le vrai objectif."
Le cycle Red/Green/Refactor en pratique .NET
Red — écrire le test qui échoue
Avant d'ouvrir le fichier d'implémentation, j'ouvre le fichier de test. Je décris ce que le code devrait faire :
[Fact]
public async Task Handle_WhenEmployeeExists_UpdatesSuccessfully()
{
// Arrange
var employee = Employee.Create("Dupont", "Jean", new DateOnly(1985, 3, 12));
await _repository.AddAsync(employee);
var command = new UpsertEmployeeCommand(
employee.Id,
LastName: "Dupont",
FirstName: "Jean-Baptiste", // changement du prénom
BirthDate: new DateOnly(1985, 3, 12)
);
// Act
await _handler.Handle(command, CancellationToken.None);
// Assert
var updated = await _repository.GetByIdAsync(employee.Id);
updated!.FirstName.Should().Be("Jean-Baptiste");
}
Ce test échoue immédiatement — le handler n'existe pas encore. C'est parfait. Je viens de spécifier le comportement attendu avant d'écrire le code.
Green — le minimum pour que le test passe
L'implémentation doit faire passer le test. Pas plus. Pas d'optimisation prématurée, pas de généralisation "au cas où". Cette contrainte force à des designs simples et révèle souvent que la feature est plus simple qu'on ne le pensait.
Refactor — améliorer sans régression
Les tests passent. Maintenant je peux améliorer : extraire une méthode, renommer une variable, supprimer une duplication. À chaque modification, dotnet test confirme que je n'ai rien cassé. Le refactoring devient serein parce qu'il est contenu dans un filet de sécurité.
Ce que ça change concrètement en mission
Le design émerge du test
Quand on écrit le test avant l'implémentation, on se retrouve à consommer son propre code depuis l'extérieur. Si le test est difficile à écrire — trop de dépendances, constructor avec 8 paramètres, impossibilité d'isoler le comportement — c'est un signal immédiat que le design est mauvais. Les tests font office de premier utilisateur du code.
Onboarding accéléré
Sur une mission de 6 mois avec 4 développeurs, un nouveau venu qui rejoint l'équipe au sprint 5 peut comprendre ce que fait le système en lisant les tests. WhenEmployeeExists_UpdatesSuccessfully : c'est une documentation vivante, exécutable, qui ne peut pas être en retard sur le code.
Refactoring sans peur
Le moment le plus courageux de ma carrière a été de refactoriser une couche complète d'infrastructure en production, sur une plateforme à fort trafic, avec 100% de confiance. Pourquoi ? Parce que 847 tests passaient en 12 secondes. Si j'avais cassé quelque chose, je l'aurais su en 12 secondes.
Les résistances que je rencontre (et comment je les gère)
- "On n'a pas le temps" — Vous n'avez pas le temps de ne pas faire de TDD. Chaque bug en production coûte 10× plus à corriger qu'un test mal écrit en amont.
- "Notre code est trop couplé pour être testé" — Exact. C'est pour ça qu'il faut le refactoriser. Le TDD révèle la dette, il ne la crée pas.
- "Les tests ralentissent les livraisons" — Sprint 1-2 : peut-être. Sprint 6+ : systématiquement plus rapide, avec zéro régression.
- "Les tests, ça marche dans les tutos mais pas sur nos vrais projets" — J'entends ça souvent. La réponse : un pair programming de 2h sur une vraie feature. Aucune équipe n'a résisté à la démonstration.
Mon protocole de test sur un projet .NET
tests/
├── Domain.Tests/ ← tests purs, ZERO dépendance externe
│ ├── Entities/ ← comportement des agrégats
│ └── ValueObjects/ ← invariants des value objects
├── Application.Tests/ ← handlers testés avec faux repositories
│ ├── Commands/
│ └── Queries/
└── Integration.Tests/ ← vraie base SQL Server via Docker
├── Api/ ← tests end-to-end HTTP
└── Persistence/ ← tests de contrat EF Core
Les tests unitaires (Domain + Application) s'exécutent en moins de 3 secondes. Les tests d'intégration en 45 secondes avec Docker. En CI/CD, les deux tournent à chaque push.
Pourquoi c'est un argument de vente pour les ESN
En tant que freelance, le TDD strict n'est pas juste une pratique technique — c'est une garantie de qualité pour le client. Je peux affirmer que le code que je livre est testé, que les régressions sont détectées automatiquement, et que la base de code sera maintenable par une autre équipe après ma mission.
C'est mesurable. C'est documentable. Et c'est ce qui différencie une prestation de commodité d'une prestation à haute valeur ajoutée.
Si vous voulez voir comment cette approche s'intègre avec Clean Architecture et comment Claude Code permet de l'appliquer encore plus efficacement, les deux articles complémentaires sont disponibles.
Vous cherchez un développeur qui garantit la qualité ?
TDD strict, code review rigoureux, zéro dette technique accumulée.
Disponible pour missions freelance full remote.