05 - TDD vs BDD vs test-after : quelle approche choisir
Ce que tu vas apprendre
- Le cycle Red-Green-Refactor du TDD en pratique
- Le Given-When-Then du BDD et quand il sert vraiment
- Pourquoi "test-after" domine dans la réalité
- Mon opinion sur quand utiliser quoi
Prerequisites
Savoir écrire des tests avec bun test. Avoir lu les articles précédents de cette serie.
Le debat sans fin
A chaque conference, a chaque forum, le meme debat. "Il faut faire du TDD." "Le TDD c'est trop rigide." "Le BDD c'est mieux." "Le BDD c'est du Gherkin que personne ne lit." J'ai pratique les trois approches en production. Aucune n'est universelle. Voici ce que j'en pense apres plusieurs annees.
TDD : Red, Green, Refactor
Le Test-Driven Development suit un cycle strict en trois étapes :
- Red : ecris un test. Il echoue (parce que le code n'existe pas encore).
- Green : ecris le minimum de code pour faire passer le test.
- Refactor : nettoie le code sans changer le comportement. Les tests te protegent.
Voyons un cycle complet. On construit un calculateur de prix avec remise.
typescript// RED : le test echoue, calculatePrice n'existe pas
import { describe, it, expect } from "bun:test";
import { calculatePrice } from "./pricing";
describe("calculatePrice", () => {
it("returns price for single item", () => {
expect(calculatePrice([{ price: 10, qty: 1 }])).toBe(10);
});
});
bashbun test
# FAIL: Cannot find module './pricing'
typescript// GREEN : le minimum pour passer
export function calculatePrice(items: { price: number; qty: number }[]): number {
return items[0].price * items[0].qty;
}
bashbun test
# PASS
Ca marche, mais le code ne gere qu'un seul item. On ajoute un test :
typescriptit("sums multiple items", () => {
expect(calculatePrice([
{ price: 10, qty: 1 },
{ price: 20, qty: 2 },
])).toBe(50);
});
bashbun test
# FAIL: expected 50, received 10
typescript// GREEN : on generalise
export function calculatePrice(items: { price: number; qty: number }[]): number {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
On continue. Remise de 10% si le total dépassé 100 :
typescriptit("applies 10% discount above 100", () => {
expect(calculatePrice([{ price: 60, qty: 2 }])).toBe(108); // 120 - 12
});
typescript// GREEN
export function calculatePrice(items: { price: number; qty: number }[]): number {
const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0);
if (subtotal > 100) return subtotal * 0.9;
return subtotal;
}
REFACTOR : le code fonctionne, on peut l'ameliorer. Extraire la logique de remise, renommer des variables. Les tests garantissent qu'on ne casse rien.
Le TDD force un design incrementiel. Tu ne construis pas une grosse architecture d'abord. Tu laisses les tests guider le design.
Quand le TDD aide vraiment : logique métier complexe, algorithmes, fonctions pures, calculs financiers. Tout ce qui a des regles precises et des cas limites.
Quand c'est trop : endpoints CRUD, code d'infrastructure, glue code, UI. Écrire un test avant un db.insert() n'apporte pas grand-chose.
BDD : Given, When, Then
Le Behavior-Driven Development formalise les tests comme des comportements. La syntaxe Gherkin :
gherkinFeature: Calcul de prix
Scenario: Remise pour commande superieure a 100 euros
Given un panier avec 2 articles a 60 euros
When je calcule le total
Then le prix est 108 euros
L'idee c'est que les specs sont lisibles par des non-techniques. Le product owner, le client, le métier. Ils ecrivent les scénarios, les devs les implementent.
En TypeScript avec un framework BDD :
typescriptdescribe("Calcul de prix", () => {
it("applique une remise pour commande superieure a 100 euros", () => {
// Given
const cart = [
{ price: 60, qty: 1 },
{ price: 60, qty: 1 },
];
// When
const total = calculatePrice(cart);
// Then
expect(total).toBe(108);
});
});
Meme sans framework Gherkin, la structure Given-When-Then rend les tests lisibles. C'est le pattern Arrange-Act-Assert (voir les tests fondamentaux) avec un vocabulaire métier.
Quand le BDD aide vraiment : quand les specs viennent de non-techniques. Quand le product owner doit valider les scénarios. Quand il y a un langage ubiquitaire partage entre devs et métier (voir la serie DDD).
Quand c'est trop : projets solo, outils internes, équipes 100% techniques. Si personne d'autre que les devs ne lit les tests, le formalisme Gherkin est du overhead pur. Sur paltemps.fr, on n'utilise pas de BDD formel. L'équipe est technique, les specs sont dans les tickets GitLab.
Test-after : la réalité du terrain
Le test-after, c'est la méthode la plus pratiquee au monde. Tu ecris le code, puis tu ajoutes des tests. C'est ce que font mes deux juniors 90% du temps. C'est ce que je fais moi-meme sur la majorite du code.
Ca fonctionne bien a une condition : tu ecris les tests. Vraiment. Pas "plus tard". Pas "quand j'aurai le temps". Maintenant, dans la meme merge request.
Le piège du test-after, tout le monde le connaît : "je testerai apres" se transforme en "je testerai quand le sprint sera moins charge" qui se transforme en "on n'a pas de tests". J'ai vu des projets entiers sans un seul test parce que l'équipe allait "ajouter les tests apres la v1".
La discipline qui marche : pas de merge request sans tests. La CI bloque si la coverage baisse (voir l'article sur le coverage). Le code review vérifié que les tests existent et testent le bon comportement.
typescript// Tu viens d'ecrire un endpoint. Maintenant, avant de push :
it("POST /missions returns 201 with valid data", async () => {
const res = await app.handle(
new Request("http://localhost/api/missions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "Mission test",
startDate: "2026-04-01",
endDate: "2026-04-05",
}),
})
);
expect(res.status).toBe(201);
const data = await res.json();
expect(data.id).toBeDefined();
});
Mon avis tranche
Voila comment je repartis sur mes projets :
TDD pour la logique métier. Calculs, regles, validations complexes, machines a états. Le cycle Red-Green-Refactor force a penser aux cas limites avant d'écrire le code. Ca produit un meilleur design.
Test-after pour tout le reste. Routes, controllers, intégration, infrastructure. Le code est souvent simple, le TDD n'apporte pas de valeur sur un db.insert().
BDD seulement si le PO écrit les specs en Gherkin. Si c'est le dev qui écrit les scénarios Gherkin pour les transformer en code, c'est de la paperasse deguisee en methodo.
En pratique sur paltemps.fr, c'est 80% test-after, 15% TDD sur le domaine, 0% BDD. Et ca marche. L'équipe est petite, technique, et les specs tiennent dans des tickets. Pas besoin de plus.
Ne choisis pas une approche par dogme. Choisis celle qui te fait écrire les tests. Parce que des tests imparfaits ecrits en test-after valent infiniment mieux que des tests TDD parfaits jamais ecrits.
Article précédent : 04 - Tests de regression
Article suivant : 06 - Tester du legacy sans tout casser