Aller au contenu principal
Lance-toi : 40% de réduction sur toutes les formations jusqu'au 30 juin

Tests et TDD : sortir de la peur de toucher au code qui marche

Tu as livré une fonctionnalité la semaine dernière. Aujourd'hui, on te demande une petite modification dessus. Et tu hésites. Pas parce que tu ne sais pas faire, mais parce que tu n'as aucun moyen de vérifier que tu ne casses rien d'autre. C'est exactement ce problème que les tests automatisés résolvent.

Guides & tutoriels ·
Adel LATIBI
Adel LATIBI

Le Briefing Dev - les ressources et actus de la semaine, droit dans ta boîte chaque vendredi gratuitement.

La plupart des développeurs juniors écrivent du code sans tests. Pas par paresse, par habitude. Les tutoriels n'en parlent presque jamais, les écoles les survolent, et les premiers projets perso fonctionnent très bien sans. Le problème arrive plus tard, quand le projet grossit, quand quelqu'un d'autre y touche, quand tu reviens sur ton propre code six mois après.

Toi, tu sens qu'il y a un truc à creuser. Tu as vu passer les termes "tests unitaires", "TDD", "couverture de code", peut-être même "tests d'intégration". Tu n'es pas sûr de la différence entre les trois. Tu te dis que c'est sûrement réservé aux gros projets ou aux développeurs séniors.

Le problème n'est pas ton niveau. C'est qu'on t'a vendu les tests comme une corvée d'expert, alors que c'est d'abord un outil de confort personnel. Cet article t'explique ce qu'il faut comprendre pour commencer à tester ton code sans avoir l'impression de perdre ton temps, et ce qu'est vraiment le TDD une fois qu'on l'a démystifié.



Le problème concret : la peur de modifier son propre code

Voici la scène que tout junior connaît. Tu reprends un fichier que tu as écrit il y a trois mois. Il fait 200 lignes. Tu dois ajouter une option, juste un petit paramètre supplémentaire. Tu fais la modification, tu relances l'application, tu testes à la main le cas que tu viens d'ajouter. Ça marche. Tu pushes.

Deux jours après, un collègue ou un utilisateur te signale qu'une autre fonctionnalité ne marche plus. Une fonctionnalité que tu n'avais pas touchée, pensais-tu. En réalité, tu avais modifié une fonction utilisée à six endroits, et tu n'as testé qu'un seul de ces endroits.

Ce scénario se répète à chaque projet sans tests. La peur de casser quelque chose finit par bloquer toute amélioration. On préfère dupliquer du code plutôt que toucher à l'existant. On laisse pourrir les vieux bouts du projet parce que personne n'ose y revenir. C'est exactement le terrain où s'installe la dette technique.

Le principe : un test, c'est juste du code qui vérifie du code

Avant tout, il faut casser une croyance répandue : un test n'est pas un outil magique, ce n'est pas non plus un truc de gourou de la qualité logicielle. Un test, c'est une petite fonction qui appelle ton code avec des entrées précises et qui vérifie que la sortie est celle que tu attends. Rien de plus.

Quand tu écris un test, tu transformes une vérification manuelle (ouvrir le navigateur, taper trois valeurs, regarder le résultat) en une vérification automatique que la machine fait pour toi en une fraction de seconde, autant de fois que tu veux. C'est l'unique vraie promesse des tests : remplacer la fatigue du test manuel par une routine fiable.

Trois familles de tests à distinguer

Les noms varient selon les communautés, mais on retrouve toujours trois niveaux :

  • Tests unitaires. Tu vérifies une seule fonction ou une seule méthode, isolée du reste. Rapides à écrire, rapides à exécuter, ils forment la majorité de tes tests.
  • Tests d'intégration. Tu vérifies que plusieurs morceaux fonctionnent ensemble : une route API avec sa base de données, par exemple. Plus lents, mais ils attrapent les bugs que les tests unitaires laissent passer.
  • Tests end-to-end. Tu simules un utilisateur qui clique dans ton application. Très lents, fragiles, mais utiles sur les parcours critiques (paiement, inscription, connexion).

Pour commencer, oublie les deux derniers niveaux. Concentre-toi sur les tests unitaires. Ils représentent 80% de l'apprentissage et 80% du bénéfice quotidien.

Ce qu'est vraiment le TDD


Test Driven Development veut dire "développement piloté par les tests". L'idée est simple : tu écris le test avant le code. Pas après. Pas en même temps. Avant.

Le cycle s'appelle "Red, Green, Refactor". Trois étapes courtes que tu répètes :

  1. Red. Tu écris un test qui décrit ce que tu veux. Tu le lances, il échoue puisque le code n'existe pas encore.
  2. Green. Tu écris le code minimum pour faire passer le test. Pas plus.
  3. Refactor. Tu nettoies le code maintenant que tu sais qu'il marche. Le test reste vert, c'est ton filet de sécurité.

Le TDD n'est pas une religion. Beaucoup de développeurs séniors n'écrivent pas en pur TDD sur tout leur code. Mais comprendre la mécanique te rend meilleur, même quand tu choisis de ne pas l'appliquer à la lettre.

Exemples concrets en JavaScript et en Python

Passons du discours au code. Voici une fonction simple à tester : un calcul de prix avec remise.

Avec Vitest en JavaScript

// prix.js
export function calculerPrix(montant, remise) {
  if (remise < 0 || remise > 100) {
    throw new Error("Remise invalide");
  }
  return montant * (1 - remise / 100);
}

// prix.test.js
import { describe, it, expect } from "vitest";
import { calculerPrix } from "./prix.js";

describe("calculerPrix", () => {
  it("applique une remise de 20 %", () => {
    expect(calculerPrix(100, 20)).toBe(80);
  });

  it("retourne le montant initial si remise nulle", () => {
    expect(calculerPrix(100, 0)).toBe(100);
  });

  it("rejette une remise négative", () => {
    expect(() => calculerPrix(100, -5)).toThrow();
  });
});

Tu lances npx vitest, tu vois passer trois lignes vertes. La prochaine fois que tu modifies calculerPrix, ces trois assertions seront rejouées automatiquement.

Avec pytest en Python

# prix.py
def calculer_prix(montant, remise):
    if remise < 0 or remise > 100:
        raise ValueError("Remise invalide")
    return montant * (1 - remise / 100)

# test_prix.py
import pytest
from prix import calculer_prix

def test_remise_de_vingt_pour_cent():
    assert calculer_prix(100, 20) == 80

def test_remise_nulle():
    assert calculer_prix(100, 0) == 100

def test_remise_negative_leve_une_erreur():
    with pytest.raises(ValueError):
        calculer_prix(100, -5)

Tu lances pytest, et tu as la même boucle de sécurité. La structure et le vocabulaire changent un peu d'un langage à l'autre, mais le principe reste rigoureusement identique.

La règle Arrange-Act-Assert

Un bon test se lit en trois temps. Tu prépares les données dont tu as besoin (Arrange). Tu exécutes la fonction testée (Act). Tu vérifies le résultat (Assert). Cette structure rend tes tests immédiatement compréhensibles, même six mois plus tard.

def test_panier_calcule_le_total():
    # Arrange
    panier = Panier()
    panier.ajouter("livre", 15)
    panier.ajouter("stylo", 3)

    # Act
    total = panier.total()

    # Assert
    assert total == 18

Les pièges fréquents quand on débute avec les tests

Vouloir tout tester d'un coup

Tu lis un article comme celui-ci, tu te motives, et tu décides de couvrir 100% de ton projet existant. Trois heures plus tard, tu abandonnes. Commence par tester une seule fonction utilitaire. Puis une autre. La couverture monte par accumulation, jamais par sprint forcé.

Tester l'implémentation au lieu du comportement

Un test qui vérifie "la fonction appelle bien tel module interne" se casse dès que tu changes l'organisation du code. Un test qui vérifie "pour cette entrée, j'obtiens cette sortie" survit aux refactorings. Toujours tester ce que le code fait, pas comment il le fait.

Confondre couverture de code et qualité des tests

Atteindre 90% de couverture ne veut pas dire que ton code est protégé. Tu peux écrire un test qui exécute toute une fonction sans jamais vérifier le résultat. La couverture est un indicateur faible, pas un objectif. Préfère dix tests pertinents à cinquante tests qui ne servent à rien.

Écrire des tests dépendants les uns des autres

Si le test B échoue parce que le test A a modifié une variable globale, tu as un problème. Chaque test doit pouvoir s'exécuter seul, dans n'importe quel ordre. Réinitialise tes états dans des fixtures ou des hooks dédiés.

Penser que le TDD ralentit

Au début, oui, tu vas perdre du temps. Au bout de quelques semaines, tu en gagnes. Tu passes moins de temps en débogage, tu refactores sans peur, tu livres avec plus de calme. Le gain est invisible sur un projet d'une semaine, énorme sur un projet d'un an.

Automatiser l'écriture des tests avec Claude Code

Une fois le réflexe acquis, la question devient pratique : comment limiter le temps passé à écrire des tests sans renoncer à leur qualité ? Sur un projet existant sans couverture, rattraper la dette à la main décourage rapidement. Claude Code, l'outil en ligne de commande d'Anthropic, permet de générer des tests directement depuis le terminal en s'appuyant sur le contexte du projet.

Génération à la demande

La commande la plus directe, depuis la racine du projet :

claude "génère les tests unitaires pour src/services/user.service.ts en suivant les conventions du projet"

Claude Code lit le fichier ciblé, identifie le framework de test utilisé (Jest, Vitest, pytest, PHPUnit) et calque le style sur les tests existants. Pour un résultat exploitable, au moins un ou deux fichiers de tests doivent déjà servir de référence.

Le fichier CLAUDE.md comme socle

Pour industrialiser, le fichier CLAUDE.md placé à la racine définit les règles que Claude doit respecter à chaque génération :

## Tests

- Framework : Vitest
- Localisation : à côté du fichier source, suffixe .test.ts
- Pattern : AAA (Arrange, Act, Assert)
- Couverture minimum : branches principales + cas d'erreur
- Mocks : utiliser vi.mock, jamais de vrais appels réseau
- Nommage : describe('NomClasse') puis it('should ... when ...')

## Commandes utiles
- Lancer les tests : pnpm test
- Coverage : pnpm test:coverage

Une fois ce fichier en place, une simple commande claude "ajoute les tests manquants pour ce fichier" produit un résultat aligné sans répéter les consignes à chaque appel.

Commandes personnalisées

Dans le dossier .claude/commands/, on crée des fichiers Markdown qui deviennent des commandes réutilisables. Exemple avec .claude/commands/test-file.md :

Génère les tests pour le fichier $ARGUMENTS.

Règles :
1. Lis d'abord un test existant du projet pour calquer le style
2. Couvre les cas nominaux et les cas d'erreur
3. Mocke toutes les dépendances externes
4. Lance les tests après génération et corrige si échec

Utilisation : /test-file src/services/auth.service.ts

Boucle agentique : génération puis correction

Claude Code peut exécuter les tests qu'il vient d'écrire et corriger les échecs en boucle. Le prompt type :

claude "écris les tests pour le module facturation, exécute-les avec pnpm test, et corrige jusqu'à ce que tout passe"

Cette approche fonctionne bien sur du code pur (calculs, transformations). Sur du code avec beaucoup d'entrées/sorties, il faut surveiller les mocks générés qui ont tendance à devenir excessifs.

Limites à connaître

Claude Code génère des tests qui passent, pas forcément des tests qui détectent les bugs. La relecture reste nécessaire pour vérifier que les assertions testent le bon comportement et pas juste l'implémentation. Sur du code legacy mal découpé, la génération produit souvent des mocks excessifs qui rendent les tests fragiles. Dans ce cas, mieux vaut refactorer d'abord, tester ensuite.

L'objectif n'est pas que Claude écrive tous les tests à ta place, mais qu'il prenne en charge la partie répétitive pour que tu puisses te concentrer sur les tests qui ont vraiment de la valeur.

Par où commencer concrètement

Le piège classique consiste à attendre "le bon projet" ou "le bon moment". Comme le décrit l'article sur le syndrome du tutoriel infini, le bon moment n'arrive jamais si tu ne le crées pas. Voici la séquence minimale :

  1. Installe le framework de test natif de ton langage : Vitest ou Jest pour JavaScript, pytest pour Python, PHPUnit pour PHP, JUnit pour Java.
  2. Repère dans ton projet une fonction pure (qui prend des paramètres et retourne un résultat, sans effet de bord). Écris trois tests dessus.
  3. Configure ton éditeur pour relancer automatiquement les tests à chaque sauvegarde. La boucle de feedback rapide est ce qui rend la pratique addictive.
  4. Branche tes tests sur ta CI/CD. Si tu utilises GitHub, regarde du côté de la formation CI/CD avec GitHub Actions pour automatiser ça proprement.

Les bonnes pratiques de tests rejoignent les principes plus larges de code propre. Si tu veux creuser ce qui rend un code testable au départ, l'article sur CQS, Command Query Separation ou celui sur DRY sont des compléments directs. Un code bien structuré est un code facile à tester.

Aller plus loin avec une formation

Les tests prennent tout leur sens dans le contexte d'un framework professionnel. Côté Python, la formation Créer une API REST avec Python et FastAPI intègre les bonnes pratiques de tests d'API. Côté PHP, Symfony 7 repose largement sur PHPUnit. Côté Java, Spring Boot embarque JUnit dès le premier projet.

Choisis l'écosystème dans lequel tu travailles déjà et plonge dedans. Les concepts de tests s'apprennent mieux ancrés dans un contexte que dans l'abstrait.

Ce qu'il faut retenir

Tester son code n'est pas un signe de manque de confiance. C'est l'inverse : c'est ce qui te permet d'avancer vite sur le long terme. Un projet sans tests devient progressivement intouchable. Un projet avec tests reste vivant.

Le TDD n'est pas une obligation, mais le pratiquer ne serait-ce qu'occasionnellement change ta façon de concevoir le code. Tu commences à penser en termes d'entrées et de sorties, de contrats clairs, de fonctions isolées. Tout ça rend ton code meilleur, même quand tu n'écris pas de tests.

Trois tests utiles valent mieux que trente tests décoratifs. Commence petit, garde la boucle de feedback rapide, et accepte que la maîtrise vient avec la répétition. Personne n'écrit de bons tests dès la première semaine.

Questions fréquentes

Faut-il tester tout son code ?

Non. Concentre-toi sur la logique métier, les fonctions utilitaires, les calculs, les transformations de données. Ne perds pas de temps à tester du code purement structurel comme un simple getter ou une ligne de configuration. La règle pratique : teste ce qui pourrait casser silencieusement.

Quelle est la différence entre Vitest, Jest et Mocha ?

Ce sont trois frameworks de tests pour JavaScript. Jest est le plus ancien et reste très utilisé. Vitest est plus récent, plus rapide et s'intègre nativement avec Vite. Mocha est minimaliste et demande de choisir séparément une bibliothèque d'assertions. Pour un nouveau projet en 2026, Vitest est généralement le choix le plus simple.

Le TDD est-il viable dans la vraie vie en entreprise ?

Oui, mais rarement à 100%. La plupart des équipes pratiquent un TDD partiel : sur les fonctions critiques, les bugs identifiés, les nouveaux modules. Le TDD complet sur l'ensemble du code est une exception, pas la norme. L'important est d'avoir une couverture de tests automatisée, peu importe l'ordre dans lequel on les écrit.

Quel pourcentage de couverture viser ?

Entre 70 et 85% sur la logique métier est un objectif raisonnable. Au-delà, tu commences à tester des choses sans valeur ajoutée. La couverture n'est pas un objectif en soi, c'est un indicateur. Mieux vaut 60% de tests qui vérifient des comportements réels que 95% de tests décoratifs.

Peut-on apprendre les tests sans avoir déjà un projet en cours ?

Oui, mais c'est moins efficace. Les tests prennent leur sens quand ils protègent du vrai code, dans un vrai projet. Si tu débutes, prends une petite app personnelle (une todo list, un convertisseur, une calculatrice) et ajoute des tests dessus. L'apprentissage par projet réel est toujours supérieur à l'apprentissage par exercices isolés.

Faut-il connaître la POO pour faire du TDD ?

Non. Le TDD s'applique à des fonctions, à des classes, à des modules. Tu peux faire du TDD avec des fonctions pures sans jamais écrire de classe. Cela dit, la POO facilite certains patterns de tests comme l'injection de dépendances. Si tu débutes, commence par tester des fonctions simples, la POO viendra naturellement après.

Vous êtes expert ?

Partagez votre expertise sur notre blog

Tutoriel, retour d'expérience, analyse - publiez un article invité et gagnez en visibilité.

Écrire pour nous