Leçon 5/9 10 min

Le rouge d'abord

Un test qui n'a jamais été rouge ne teste rien. Le piège du test fantôme, et pourquoi on écrit (parfois) le test avant le code.

Tout au vert, et pourtant…

Tu ouvres le terminal. Vingt tests. Vingt coches vertes. Un sentiment de maîtrise, presque de fierté. Puis tu regardes de plus près et tu tombes sur ça :

test('ça marche', () => {
  expect(true).toBe(true);
});

Ce test passe toujours. Peu importe ce que ton code fait. Peu importe si tu le supprimes, si tu l'inverses, si tu le remplaces par de la bouillie. true sera toujours égal à true : ce test n'a jamais vu le rouge et ne le verra jamais. C'est un test fantôme : il compte dans le total, il rassure l'œil, et il ne teste absolument rien.

Le piège du faux sentiment de sécurité : un tableau tout au vert peut cacher des tests fantômes, des assertions absentes, ou des tests qui vérifient l'entrée au lieu de la sortie. La couleur verte ne dit pas « mon code est correct ». Elle dit « aucun de ces tests n'a trouvé de problème ». Ce n'est pas la même chose.

Un test qui n'a jamais été rouge ne prouve rien

Voilà la règle que tu dois retenir de cette leçon : un test doit avoir été rouge au moins une fois, et pour la bonne raison. Pourquoi ?

Un test peut passer au vert pour trois mauvaises raisons :

  • Pas d'assertion. test('ça tourne', () => { maFonction(); }) : aucun expect, donc jamais rouge. Il vérifie juste que le code ne plante pas, pas qu'il renvoie quelque chose de juste.
  • Assertion triviale. expect(true).toBe(true), ou expect(result).toBeDefined() : vert tant que la fonction renvoie quelque chose, même une valeur complètement fausse.
  • Faute de frappe dans le nom. Tu testes calulerTotal() au lieu de calculerTotal() : la fonction testée n'est pas celle que tu crois, et le test passe vert sur du vide.

Le rouge, lui, prouve deux choses en même temps : que le test sait détecter un problème, et qu'il le détecte pour la bonne raison, pas par accident.

« passer au rouge » ne signifie pas que ton code est mauvais. Ça signifie que ton test est honnête. Le rouge est une bonne nouvelle : il dit « je surveille vraiment quelque chose ».

Le rouge d'abord, en pratique

Voici une fonction vide (un stub) et deux tests déjà écrits. Lance-les : ils sont rouges. La fonction ne renvoie rien, donc elle renvoie undefined, pas true ni false. C'est exactement ce qu'on veut voir d'abord. Ensuite, écris le minimum de code pour les faire passer au vert.

Prédis avant de lire

La fonction estPositif est vide (elle ne fait aucun return). Que renvoie-t-elle, et donc que vont afficher les tests au premier lancement ? Écris ta prédiction avant de dérouler.

Voir la réponse

Une fonction sans return renvoie undefined. Les deux tests passent au rouge : le premier attendait true, le second attendait false, tous les deux ont reçu undefined. C'est exactement ce qu'on veut voir d'abord : le rouge prouve que les tests savent détecter l'absence du bon comportement. Ils ne passent pas au vert par accident, ils travaillent vraiment.

🧪 Labo de test · écris le code, lance, observe
Code à compléter
Tests
Bloqué sur le fix ? Voir la correction

Ajoute return n > 0; dans le corps de la fonction. Une seule ligne suffit : elle renvoie true si n est strictement positif, false sinon. Les deux tests passent au vert. Tu viens de faire exactement ce que fait TDD : le test t'a dit quoi écrire, et tu as écrit le minimum pour satisfaire le contrat.

Écrire le test avant le code

Dans le labo, les tests existaient déjà quand tu as commencé à coder. Tu as vu le rouge, puis tu as écrit le minimum pour le faire disparaître. C'est le principe de base du TDD (Test-Driven Development) : le test vient en premier, le code suit.

Pourquoi écrire le test avant le code ? Parce que le test devient une spécification exécutable. Avant même d'écrire une ligne de logique, tu décris ce que la fonction doit faire. Le test reste rouge tant que ce n'est pas vrai. Quand il passe au vert, tu as fini. Pas de doute, pas de « je crois que ça marche » : le rouge t'a dit que tu n'avais pas fini, le vert te dit que tu as fini.

C'est l'étape RED du cycle TDD : on en parlera en détail à la leçon suivante. Mais retiens déjà l'essentiel :

Avant de faire confiance à un test, vérifie qu'il sait passer au rouge. Casse temporairement le code (change le résultat attendu, vide la fonction, inverse la condition). Si le test reste vert, il ne teste rien. Un test de confiance est un test que tu as déjà vu échouer, pour la bonne raison.

Mauvais : Code puis Test vert sans jamais voir le rouge : on ignore s'il détecte quoi que ce soit. Bon : Test rouge d'abord, puis Code, puis Test vert, et on sait qu'il sait échouer. MAUVAIS Code d'abord Test ✓ vert jamais vu rouge On ignore s'il détecte quoi que ce soit. Confiance : zéro. BON Test ✗ rouge d'abord Code Test ✓ vert Il SAIT échouer : confiance gagnée. Le vert veut vraiment dire quelque chose.
Un test de confiance est un test qu'on a déjà vu rouge, pour la bonne raison.

All green, and yet…

You open the terminal. Twenty tests. Twenty green checkmarks. A feeling of control, almost pride. Then you look closer and spot this:

test('it works', () => {
  expect(true).toBe(true);
});

This test always passes. Whatever your code does. Whether you delete it, invert it, replace it with gibberish. true will always equal true: this test has never seen red and never will. It's a phantom test — it counts in the total, it reassures the eye, and it tests absolutely nothing.

The false sense of security trap: an all-green board can hide phantom tests, missing assertions, or tests checking the input instead of the output. The green colour doesn't say "my code is correct" — it says "none of these tests found a problem". That's not the same thing.

A test that was never red proves nothing

Here's the rule to take away from this lesson: a test must have been red at least once, and for the right reason. Why?

A test can be green for three wrong reasons:

  • No assertion. test('it runs', () => { myFunction(); }) — no expect, so never red. It only checks the code doesn't crash, not that it returns the right thing.
  • Trivial assertion. expect(true).toBe(true), or expect(result).toBeDefined(): green as long as the function returns something, even a completely wrong value.
  • Typo in the name. You test caluateTotal() instead of calculateTotal(): the function under test isn't the one you think, and the test goes green on thin air.

Red, on the other hand, proves two things at once: the test knows how to detect a problem, and it detects it for the right reason — not by accident.

"going red" doesn't mean your code is bad — it means your test is honest. Red is good news: it says "I'm really watching something".

Red first, in practice

Here's an empty function (a stub) and two tests already written. Run them: they're red — the function returns nothing, so it returns undefined, not true or false. That's exactly what you want to see first. Then write the minimum code to make them go green.

Predict before reading on

The isPositive function is empty (no return). What does it return, and what will the tests show on the first run? Write your prediction before expanding.

Show the answer

A function with no return returns undefined. Both tests go red: the first expected true, the second expected false, both received undefined. That's exactly what you want to see first: red proves the tests know how to detect missing behaviour. They don't go green by accident — they're genuinely working.

🧪 Test lab · write the code, run, observe
Code to complete
Tests
Stuck on the fix? Show it

Add return n > 0; inside the function body. One line is enough: it returns true if n is strictly positive, false otherwise. Both tests go green. You just did exactly what TDD does: the test told you what to write, and you wrote the minimum to satisfy the contract.

Writing the test before the code

In the lab, the tests existed before you started coding. You saw red, then wrote the minimum to make it disappear. That's the core principle of TDD (Test-Driven Development): the test comes first, the code follows.

Why write the test before the code? Because the test becomes an executable specification. Before writing a single line of logic, you describe what the function must do — and the test stays red until it's true. When it goes green, you're done. No doubt, no "I think it works": red told you you weren't finished, green tells you you are.

This is the RED step of the TDD cycle — we'll cover it in detail in the next lesson. But keep the key takeaway now:

Before trusting a test, check it knows how to go red. Temporarily break the code (change the expected value, empty the function, flip the condition) — if the test stays green, it tests nothing. A trustworthy test is one you've already seen fail, for the right reason.

Bad: Code then green Test without ever seeing red — we don't know whether it detects anything. Good: Red test first, then Code, then green Test — we know it knows how to fail. BAD Code first Test ✓ green never seen red We don't know if it detects anything. Trust: zero. GOOD Test ✗ red first Code Test ✓ green It KNOWS how to fail: trust earned. Green actually means something.
A trustworthy test is one you've already seen red — for the right reason.

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Avec tes mots : pourquoi un test qui n'a jamais été rouge ne prouve rien ?

Une bonne explication dit : s'il n'a jamais échoué, on ne sait pas s'il peut échouer. Il peut être vert par accident : pas d'assertion, assertion triviale (expect(true).toBe(true)), ou faute de frappe dans le nom testé. Le voir rouge une fois prouve qu'il détecte bien le problème, et pour la bonne raison.
🧠 Rappel libre
Rappel libre

Sans remonter : c'est quoi un test fantôme ? Donne un exemple.

Un test fantôme est un test qui passe toujours quel que soit le code, donc ne détecte rien. Exemples : expect(true).toBe(true) (assertion sur une constante), un test sans aucune assertion, ou un test qui vérifie l'entrée au lieu de la sortie. Il compte dans le total vert mais n'apporte aucune garantie.
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

Pour une fonction qui doit renvoyer 90, l'IA écrit : test('ok', () => { expect(appliquerCoupon(100, 'PROMO10')).toBeDefined(); }) et dit : « C'est testé, c'est vert. » Tu acceptes, ou tu rejettes ?

À rejeter. toBeDefined() est une assertion trop faible : elle passe tant que la fonction renvoie quelque chose, même -900. Ce test serait vert avec un code faux : il n'a jamais pu être rouge pour la bonne raison. Il faut expect(...).toBe(90) : là, le test sera rouge tant que le code est faux, et vert seulement quand il renvoie vraiment 90.
Un test passe au vert du premier coup, tu ne l'as jamais vu rouge. Pourquoi se méfier ?
Que vaut le test expect(true).toBe(true) ?
Pourquoi vouloir voir un test ÉCHOUER d'abord ?
Écrire le test AVANT le code, quel intérêt ?
Prochaine étape

Tu as vu le rouge, tu l'as fait passer au vert. Mais TDD, c'est trois étapes : rouge, vert… et refactor. À la leçon 6, on parcourt le cycle complet, et on pose la vraie question : faut-il vraiment tout faire en TDD ?

Leçon 6 : Le cycle TDD →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement