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)

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.

✉ Discutons de votre projet Pourquoi me choisir →