Riadh Mnasri
← Retour au blog
4 min de lecture

Pourquoi le TDD n'est pas une option quand on livre vite

L'objection que j'entends le plus souvent sur le TDD, c'est qu'il ralentit quand on est pressé. En pratique, c'est l'inverse : c'est justement sous pression de deadline qu'écrire le test avant le code fait gagner du temps, pas en perdre.

Ce que la pression change vraiment

Sous pression, ce qu'on sacrifie en premier, ce n'est pas le temps de coder, c'est le temps de réfléchir à ce qu'on est en train de construire. On code plus vite, mais on code flou : le périmètre du changement se précise au fur et à mesure qu'on écrit, au lieu d'être clair avant. On découvre les cas limites en production, au pire moment possible, plutôt qu'en écrivant le test.

Écrire le test avant l'implémentation force cette clarification à se faire en premier, quand elle coûte le moins cher. Un test qu'on n'arrive pas à écrire clairement est souvent le signe qu'on n'a pas encore compris le problème, pas un problème d'outillage. C'est un signal précoce, exactement quand on en a le plus besoin.

Le vrai coût n'est pas où on croit

La critique du TDD suppose que le coût se mesure en temps d'écriture du code. Mais sur une mission freelance, le coût qui compte vraiment, c'est le temps de debug en production et le temps de compréhension du prochain développeur qui reprend le code six mois plus tard, sans le contexte qu'on avait en tête au moment d'écrire la fonctionnalité. Un test qui documente un cas limite rembourse cet investissement très vite, souvent dès le premier bug évité, et sert ensuite de documentation vivante pour quiconque touche ce code après nous.

Un exemple concret

Pour une règle qui refuse une contrepartie dont l'exposition dépasse sa limite, la convention Given/When/Then donne un test qui se lit comme une spécification, avant même d'écrire l'implémentation :

describe("validation de la limite de risque", () => {
  it("refuse une exposition qui dépasse la limite de la contrepartie", () => {
    // Given une contrepartie avec une limite de 1 000 000
    const counterparty = aCounterparty({ riskLimit: 1_000_000 });

    // When on valide une exposition de 1 200 000
    const result = validateExposure(counterparty, { amount: 1_200_000 });

    // Then la validation échoue avec le motif explicite
    expect(result).toEqual({ valid: false, reason: "LIMIT_EXCEEDED" });
  });
});

Ce test ne teste rien d'implémenté : il force à décider, avant d'écrire une seule ligne de logique, ce que "dépasser la limite" veut dire exactement (strictement supérieur ? à partir de quel seuil ?) et ce que le système doit répondre. C'est cette décision, prise tôt et explicitement, qui manque quand on code l'implémentation d'abord et qu'on ajoute des tests après coup.

Où le coût se déplace, pas où il disparaît

Code d'abord, tests aprèsTest d'abord (TDD)
Vitesse d'écriture initialePlus rapide en apparenceLégèrement plus lente au départ
Moment de clarification du besoinPendant le debug, souvent en productionAvant la première ligne de code
Coût d'un cas limite oubliéBug découvert par un utilisateur ou un incidentAbsence de test qui saute aux yeux en revue
Documentation du comportement attenduReconstituée après coup, si quelqu'un s'en donne la peineLe test lui-même, à jour par construction

Ce tableau résume l'objection la plus fréquente sous un autre angle : le TDD ne fait pas disparaître le coût de la réflexion sur les cas limites, il le déplace simplement avant l'écriture du code, quand il coûte le moins cher à corriger.

Ce que ça change concrètement dans mon flux

Sur mes projets personnels comme sur mission, la même discipline s'applique : un cas d'usage n'est pas fini tant que son test ne décrit pas explicitement son comportement attendu, y compris les cas d'erreur. J'utilise systématiquement la convention Given/When/Then, qui oblige à nommer explicitement le contexte de départ, l'action déclenchante, et le résultat attendu, avant même d'écrire une ligne d'implémentation.

Sur MissionMatch ou kotlin-counterparty-risk, chaque cas d'usage du domaine a été écrit test d'abord. Le bénéfice devient particulièrement visible sur les calculs financiers : sans un test qui fixe le comportement attendu pour un cas limite (un notionnel négatif, une date d'échéance passée, un flux de collatéral nul), il est facile de livrer un calcul qui semble correct sur les cas courants et se trompe silencieusement sur les cas rares.

Ce que je réponds à la pression

Quand une mission impose une deadline serrée, la tentation est de sauter les tests pour aller plus vite. Mon expérience est systématiquement l'inverse : c'est justement là que le TDD protège le plus, parce qu'il empêche d'accumuler une dette qu'on n'aura pas le temps de rembourser plus tard, une fois la deadline passée et l'attention retombée. Ce n'est pas du perfectionnisme : c'est la seule façon que je connaisse de garder de la vitesse sur la durée, plutôt que de l'échanger contre de la dette qu'on rembourse plus tard, avec intérêts.