TDD expliqué simplement
Le TDD (Test-Driven Development) consiste à écrire un test qui échoue avant d’écrire le code qui le fait passer, puis à améliorer la structure une fois le comportement validé. Ce cycle court — Red, Green, Refactor — guide la conception au lieu de la vérifier après coup.
Sommaire
Le problème #
L’approche naturelle consiste à écrire d’abord le code, puis, si le temps le permet, à ajouter des tests après coup. Dans la pratique, “si le temps le permet” devient souvent “jamais” : une fois la fonctionnalité livrée, l’attention passe au ticket suivant. Et quand des tests sont écrits après coup, ils ont tendance à confirmer ce que le code fait déjà — y compris ses bugs — plutôt qu’à vérifier ce qu’il devrait faire.
Il manque un mécanisme qui force à clarifier le comportement attendu avant d’écrire la première ligne d’implémentation, et qui donne un signal objectif du moment où le travail est terminé.
L’idée générale #
Le TDD (Test-Driven Development) inverse l’ordre habituel : on écrit un test qui décrit le comportement attendu, on le voit échouer (puisque le code n’existe pas encore), puis on écrit le code minimal nécessaire pour le faire passer. Ce cycle se répète en trois étapes courtes :
- Red : écrire un test qui décrit un comportement pas encore implémenté. Il échoue — c’est normal et attendu, ça prouve que le test teste bien quelque chose.
- Green : écrire le code le plus simple possible pour faire passer ce test, sans se soucier de l’élégance. L’objectif est uniquement que la suite de tests soit au vert.
- Refactor : une fois le test au vert, nettoyer le code (noms, duplication, structure) sans changer son comportement. Les tests déjà écrits garantissent que rien ne casse pendant cette étape.
On répète ensuite ce cycle pour le comportement suivant. Chaque itération est volontairement petite — quelques minutes — ce qui garde un rythme rapide et un feedback immédiat.
Analogie du quotidien #
Le TDD, c’est comme se fixer un itinéraire avant de démarrer un trajet plutôt que de rouler et vérifier après coup si on est arrivé au bon endroit. On définit d’abord la destination précise (le test : “je veux arriver ici”), puis on choisit le chemin le plus direct pour y parvenir (le code minimal), puis, une fois arrivé, on peut ajuster l’itinéraire pour éviter les bouchons la prochaine fois (le refactoring) — sans jamais changer la destination elle-même.
Rouler d’abord et vérifier ensuite si on est au bon endroit fonctionne parfois, mais on ne le sait qu’après coup, une fois le trajet terminé.
Diagramme #
Exemple de code #
PHP #
// Étape 1 - RED : on écrit le test avant que la classe existe
use PHPUnit\Framework\TestCase;
final class PanierTest extends TestCase
{
public function testUnPanierVideATotalZero(): void
{
$panier = new Panier();
$this->assertSame(0.0, $panier->total());
}
}
// À ce stade, `Panier` n'existe pas : le test échoue (erreur de classe manquante).
// Étape 2 - GREEN : le code minimal pour faire passer le test
final class Panier
{
public function total(): float
{
return 0.0;
}
}
// Le test passe. C'est volontairement simpliste : on n'anticipe rien de plus
// que ce que le test actuel exige.
// On ajoute un nouveau test pour pousser le comportement plus loin (nouveau cycle RED)
public function testAjouterUnArticleAugmenteLeTotal(): void
{
$panier = new Panier();
$panier->ajouter(new Article('Livre', 15.0));
$this->assertSame(15.0, $panier->total());
}
// Étape GREEN suivante : le code évolue pour répondre au nouveau test,
// sans casser le précédent (`testUnPanierVideATotalZero` reste vert)
final class Panier
{
/** @var Article[] */
private array $articles = [];
public function ajouter(Article $article): void
{
$this->articles[] = $article;
}
public function total(): float
{
return array_sum(array_map(fn (Article $a) => $a->prix(), $this->articles));
}
}
JavaScript #
// Étape 1 - RED : le test décrit un comportement qui n'existe pas encore
const { Panier } = require("./panier");
describe("Panier", () => {
it("a un total de 0 quand il est vide", () => {
const panier = new Panier();
expect(panier.total()).toBe(0);
});
});
// À ce stade, `./panier` n'existe même pas : le test échoue.
// Étape 2 - GREEN : le code minimal pour faire passer le test
class Panier {
total() {
return 0;
}
}
module.exports = { Panier };
// Nouveau cycle RED : on pousse le comportement plus loin
it("additionne le prix des articles ajoutés", () => {
const panier = new Panier();
panier.ajouter({ nom: "Livre", prix: 15 });
expect(panier.total()).toBe(15);
});
// GREEN suivant : le code évolue sans casser le test précédent
class Panier {
constructor() {
this.articles = [];
}
ajouter(article) {
this.articles.push(article);
}
total() {
return this.articles.reduce((somme, a) => somme + a.prix, 0);
}
}
module.exports = { Panier };
Quand utiliser le TDD ? #
- Sur une logique métier dont le comportement attendu est connu à l’avance : le TDD excelle à clarifier des règles précises (calculs, validations, transitions d’état).
- Quand on veut garantir une bonne couverture de tests par construction, plutôt que d’espérer la rattraper après coup.
- Sur un code destiné à évoluer souvent : le filet de tests produit par le TDD facilite chaque refactoring futur.
- Moins adapté à l’exploration pure (prototypage d’UI, expérimentation d’API externe méconnue) où le comportement attendu n’est pas encore clair — dans ce cas, explorer d’abord, puis stabiliser au TDD une fois la direction connue, est souvent plus efficace.
Points importants #
- L’étape Red n’est pas optionnelle : voir le test échouer avant d’écrire le code confirme que le test teste réellement quelque chose de nouveau.
- Le code écrit à l’étape Green doit rester minimal — céder à la tentation de généraliser trop tôt casse le rythme court du cycle et introduit du code non couvert par un test.
- Le Refactor s’appuie entièrement sur les tests déjà verts : sans eux, refactorer devient aussi risqué qu’avant le TDD.
- Le TDD est une discipline de conception autant qu’une pratique de test : le vrai bénéfice est la clarté qu’il impose sur le comportement attendu, avant même d’écrire l’implémentation.
🐻 À retenir
- ●Le cycle TDD tient en trois mots : Red (test qui échoue), Green (code minimal qui le fait passer), Refactor (nettoyer sans changer le comportement).
- ●En TDD, le test n’est pas une vérification a posteriori : il précède le code et guide sa conception.
- ●La règle d’or du Green : écrire le code le plus simple possible pour faire passer le test, jamais plus — la généralisation vient plus tard, poussée par de nouveaux tests.