
Introduction
Après nos articles sur la Clean Architecture et le Domain-Driven Design (DDD), nous allons poursuivre notre exploration des principes d’architecture et de développement en jetant un œil aux tests et à la manière dont ils peuvent nous aider à structurer le code.
Nous allons tenter de comprendre d’où provient cette philosophie, ce qu’elle est et ce qu’elle apporte, au travers d’exemples de code.
Un peu d’histoire
Le Test-Driven Development, ou TDD, est issu d’une lignée de méthodes de travail remontant aux années 1990, qu’on nomme généralement l’Exreme Programming (XP). Kent Beck, alors chez Chrysler, développe l’Extreme Programming en poussant les idées des bonnes pratiques au maximum. Il met notamment en avant le principe de déplacer les tests au plus tôt, voire avant la production de code. Il est également accompagné par deux grands noms : Ron Jeffries et Ward Cunningham, ce dernier ayant théorisé l’idée de dette technique.
Kent Beck publie en 2002 le livre fondateur du TDD : Test‑Driven Development: By Example où il formalise le cycle Red-Green-Refactor que nous étudierons dans cet article. Ainsi, le TDD devient une compétence “core” des développeurs Agile.
Ici, Agile fait référence au Manifeste Agile dont Kent Beck, Ward Cunningham et Ron Jeffries sont co-auteurs avec Martin Fowler et Robert C. Martin que nous avons évoqués dans les articles précédents.
En 20 ans, nous avons vu apparaitre de nombreux outils et de nouvelles méthodes héritères du travail de Kent Beck :
Frameworks de tests facilitant la mise en œuvre du TDD
En C#, nous avons MS Test, NUnit, xUnit
Behaviour-Driven Development (BDD) et Specification by Example
Une méthode décrivant le métier et le comportement utilisateur servant de moteur aux tests
Les tests émergent d’exemples concrets plutôt que de notions abstraites
Domain-Driven Design (DDD)
En explorant le domaine, nous pouvons faire émerger au fur et à mesure des cas métier concrets, servant à l’écriture de tests
Shift-Left Testing et Intégration Continue (CI)
Développement et exécution des tests plus tôt et tout au long de l’écriture et du déploiement du code
C’est quoi le TDD ?
Le Test-Driven Development est, par essence, une méthode de conception pilotée par les tests. Le principe fondamental se nomme le cycle Red-Green-Refactor dont les objectifs sont multiples :
Spécification vivante : Le test décrit ce que le code doit faire, avant même de savoir comment il le fera
Design incrémental : Les tests forcent la création de petites unités cohérentes réduisant la complexité
Confiance : Un code couvert par des tests qui passent garantit la non-régression
Documentation automatisée : Les scénarios de test sont une source de vérité sur les exigences fonctionnelles
Refactoring sécurisé : Permet de ré-architecturer en toute confiance tant que les tests restent verts
Red-Green-Refactor

Le cycle Red-Green-Refactor est un élément central et impose une manière de travailler. Ainsi, la règle d’or qui en découle est qu’il ne faut pas écrire de code de production sans avoir écrit un test qui guide son développement.
Il se décline en trois grandes phases :
RED : Écrire un test qui échoue et/ou ne compile pas pour s’assurer que le besoin n’est pas déjà satisfait.
GREEN : Implémenter le minimum de code pour valider le test sans se préoccuper de la qualité du code.
REFACTOR : Améliorer la lisibilité, la maintenabilité, la duplication et les performances sans changer le comportement observable.
Implémenter le TDD en .NET
Nous allons illustrer le TDD appliqué à la couche Domain d’une application fictive de gestion de commandes pour une boutique en ligne.
Pour cet exemple, le scénario métier est le suivant :
Une commande peut être créée uniquement si le client possède suffisamment de crédit.
RED - Créer le test en échec

Le test est en échec : il ne compile pas car les classes, Order, Money, Quantity et DomainException n’existent pas.
GREEN - Implémentation minimale
Pour ne pas alourdir l’article, les classes Money, Quantity et DomainException ne seront pas implémentées : elles sont relativement triviales.
Si vous avez suivi notre précédent article sur DDD, Money et Quantity sont des Value Objects.

Le test est validé, le compilateur permet de builder le code sans erreur, l’exception est levée avec le bon message.
Attention ! Ici, l’implémentation est volontairement naïve. Nous aurions dû vérifier que le crédit est supérieur au prix unitaire multiplié par la quantité mais l’objectif de l’étape GREEN est de valider le test de manière stricte. La règle de calcul n’a pas été spécifiée et un test devrait être écrit avant d’implémenter cette règle.
REFACTOR - Amélioration du design
Sans plus d’éléments métier, l’implémentation est suffisante mais quelques éléments pourraient être améliorés :
Factorisation : Extraire la règle de validation dans une Spécification (Martin Fowler – Specification) ou dans un Domain Service
Factory : Éviter de créer des objets Order via le constructeur mais utiliser une Factory, ici on l’écrira sous la forme d’une méthode statique de la classe Order
Encapsulation : Supprimer le setter de la propriété Credit et la rendre readonly

La définition d’une spécification permet de formaliser la règle métier en respectant le Single Responsibility Principlemême si, dans ce cas précis, ce serait tomber dans l’over engineering. Mais dans un projet d’envergure, ce pattern peut rendre le code plus lisible et plus maintenable.
La méthode Create dans la classe Order permet de décharger le constructeur d’éventuelles règles métier qu’il faudrait valider dès la construction de l’objet. Ceci provient principalement de DDD qui impose une stricte validité des objets du domaine à chaque instant. Ainsi, avec cette méthode, nous ne construirons que des instances valides sinon, il suffira de lever une exception.
Répétition du cycle
Après avoir effectué ce cycle dans le développement de notre fonctionnalité métier, nous pouvons le réitérer en ajoutant de nouveaux tests.
Une bonne pratique à observer lorsqu’on fait du TDD, mais aussi de manière plus générale, c’est de considérer chaque cas métier comme étant un test distinct. Ils doivent être indépendants, lisibles et automatisés si possible dans un pipeline CI.
Par exemple, dans une nouvelle phase RED, nous pouvons ajouter le test validant le cas métier où la quantité de produit à ajouter est plus grande que 1 pour prendre en compte le total commandé (quantité x prix unitaire).

Le test échouant, il faut changer le code pour prendre en compte ce nouveau cas dans la phase GREEN du cycle.

Suite à notre phase de REFACTOR, seule la spécification doit évoluer pour prendre en compte la nouvelle règle de calcul. Évidemment, son utilisation dans la méthode AddItem doit également être modifiée, ce qui nous donne des pistes pour la phase REFACTOR du cycle en cours.

Nous avons ajouté un nouveau type OrderedProduct qui représente un produit commandé, qui pourra servir plus tard à construire la liste des produits d’une commande, mais surtout, il nous est utile pour mieux formaliser notre domaine.
En réitérant le cycle Red-Green-Refactor, nous poursuivons l’élaboration d’un code testé, maintenable, répondant aux attentes du métier en garantissant un haut niveau de qualité.
Avantages et inconvénients du TDD
Nous l’avons vu, le TDD est une méthode de conception puissante offrant de nombreux avantages :
Spécification vivante : Chaque règle métier est exprimée sous la forme d’un ou plusieurs tests.
Design incrémental : Les tests obligent à découper le domaine en petites unités.
Confiance lors des refactorisations : L’ensemble des tests agit comme un filet de sécurité face aux régressions.
Documentation automatique : Les spécifications deviennent la source de vérité pour les développeurs et les product owners.
Révélation précoce des violations de contrat : Les exceptions métier sont testées dès le début de l’écriture du code.
Alignement avec la Clean Architecture et le DDD : Les règles métier restent dans le cœur (Domain) sans dépendance aux autres composants applicatifs.
En revanche, le TDD n’est ni sans défauts ni sans risque :
Courbe d’apprentissage : Il faut adopter un mindset “test-first” et résister à la tentation d’implémenter d’abord et de tester ensuite.
Tests trop détaillés : Écrire des tests qui couvrent chaque ligne de code plutôt que chaque scénario métier peut conduire à des tests fragiles.
Temps de mise en place initial : Le coût d’écriture des premiers tests peut sembler lourd, surtout si le domaine est complexe.
Dépendance à la suite de tests : Si le jeu de tests devient conséquent, les temps d’exécution des pipelines CI peuvent augmenter fortement.
Sur-spécification : Vouloir couvrir chaque scénario possible peut rendre les tests incompréhensibles et difficiles à maintenir.
Gestion des dépendances : Les tests d’intégration ont besoin du domaine, ce qui oblige à créer des tests en doublon ou des fixtures complexes.
Conclusion
Le Test-Driven Development n’est pas une méthode de tests supplémentaire, c’est une philosophie de conception qui place le comportement métier au cœur du processus de développement.
Pour faire le lien avec nos précédents articles sur la Clean Architecture et sur le Domain-Driven Design, en combinant le TDD avec la Clean Architecture et le DDD, on obtient :
Une séparation stricte des préoccupations : Les règles du domaine restent isolées, testables et indépendantes de toute infrastructure
Un design naturellement orienté domaine : Chaque test décrit une règle métier, ce qui conduit à des agrégats, des entités et des Value Objects bien définis
Une base solide pour les couches Application et Infrastructure : Les tests du domaine garantissent que les use cases ne peuvent pas violer les invariants métier
Adopter le TDD dès la couche Domain, c’est investir du temps aujourd’hui pour économiser des heures de débogage et de refactorisation à l’avenir. C’est aussi offrir aux équipes un langage commun entre les développeurs, les architectes, et les product owners. Les tests deviennent le contrat vivant qui guide la conception de votre produit.
Pour aller plus loin, je vous invite à jeter un œil au Behaviour-Driven Development, qui étend le TDD aux product owners et aux business analysts en leur permettant d’écrire les scénarios de tests en langage “presque” naturel.
Sources
Test Driven Development: By Example par Kent Beck
Test Driven Development - https://martinfowler.com/bliki/TestDrivenDevelopment.html
Is TDD Dead? - https://martinfowler.com/articles/is-tdd-dead/
Understanding the differences between BDD & TDD - https://cucumber.io/blog/bdd/bdd-vs-tdd/
Test-Driven Development Example - https://microsoft.github.io/code-with-engineering-playbook/automated-testing/unit-testing/tdd-example/