Les tests automatisés
Comprendre la pyramide des tests, écrire des tests unitaires lisibles, faire du TDD, automatiser l'intégration et le end-to-end. Pour pouvoir refactorer sans angoisse.
À la fin du cours, tu sais
- Expliquer ce qu'un test fait gagner (régression, refacto, doc vivante)
- Écrire un test unitaire propre selon le pattern AAA
- Pratiquer le TDD (red → green → refactor)
- Distinguer test unitaire, d'intégration et e2e
- Mettre en place des tests Playwright sur les parcours critiques
- Brancher les tests en CI sans gaspiller des minutes
Prérequis
- Maîtriser JavaScript ou TypeScript
- Avoir un projet sur lequel pratiquer (ou en créer un minimal pour le cours)
Chapitre 1
Pourquoi tester ton code
Un test, c'est un filet de sécurité qui te laisse refactorer sans angoisse. Sans tests, chaque modification est une roulette russe.
- Anti-régression : tu modifies une fonction, le test te dit immédiatement si tu casses l'existant
- Refacto sereine : tu changes l'implémentation sans changer le comportement, les tests verts valident
- Documentation vivante : un test bien nommé décrit l'intention métier mieux qu'un commentaire
- Confiance en prod : merger un vendredi soir sans avoir la boule au ventre
- Feedback de design : un code difficile à tester est souvent un code mal découpé
Le test comme premier·e utilisateur·ice de ton code
Chapitre 2
La pyramide des tests
Beaucoup de tests rapides à la base, peu de tests lents au sommet. Inverser la pyramide ralentit la CI et rend les tests fragiles.
- Unitaires (≈ 70 %) : rapides (ms), isolés, testent une fonction pure ou une classe
- Intégration (≈ 20 %) : vérifient que plusieurs modules collaborent (DB, API interne)
- End-to-end (≈ 10 %) : simulent un·e utilisateur·ice à travers toute la stack
L'anti-pattern du cornet de glace
Quel test pour quel cas
- Une fonction de calcul (ex : TVA, panier) → unitaire
- Une couche d'accès à une base ou un appel d'API interne → intégration
- Un parcours utilisateur critique (login, paiement) → e2e
Chapitre 3
Tests unitaires : le pattern AAA
Une fonction, un comportement, un test lisible en 5 secondes. Le pattern Arrange / Act / Assert structure tes tests pour qu'ils restent compréhensibles à long terme.
AAA : Arrange, Act, Assert
import { describe, it, expect } from 'vitest';
import { calculerTVA } from './tva';
describe('calculerTVA', () => {
it('applique 20% sur un montant HT', () => {
// Arrange : préparer les données et le contexte
const ht = 100;
// Act : exécuter la fonction testée
const ttc = calculerTVA(ht, 0.2);
// Assert : vérifier le résultat
expect(ttc).toBe(120);
});
});Les règles d'un bon test unitaire
- Isolation : pas de DB, pas de réseau, pas d'horloge réelle
- Déterministe : 100 exécutions = 100 résultats identiques
- Rapide : on doit pouvoir lancer toute la suite en quelques secondes
- Nom clair :
it('renvoie 0 quand le panier est vide')>it('test calcul') - Une seule chose testée : un test = un comportement attendu
Mocks et spies pour isoler
import { vi } from 'vitest';
import { envoyerEmail } from './email';
import { creerUtilisateur } from './users';
vi.mock('./email');
it('envoie un email de bienvenue à l\'inscription', async () => {
await creerUtilisateur({ email: 'alice@exemple.fr' });
expect(envoyerEmail).toHaveBeenCalledWith({
to: 'alice@exemple.fr',
template: 'bienvenue',
});
});Chapitre 4
Le TDD : Red, Green, Refactor
Écrire le test avant le code, c'est laisser le test guider le design. Le code se dessine plus proprement.
Le cycle en 3 étapes
- Red : tu écris un test qui échoue (la fonction n'existe pas encore, ou ne fait pas ce qu'il faut)
- Green : tu écris le minimum de code pour passer au vert. Pas plus.
- Refactor : tu nettoies le code et les tests, sans changer le comportement
Baby steps
Un comportement à la fois, pas dix cas d'un coup. Tu ajoutes un test, tu fais passer, tu nettoies. Boucle suivante. Au début ça paraît lent, à la fin du projet tu as un design propre, modulaire, testable.
Le bénéfice non-évident du TDD
Exemple : implémenter un panier en TDD
import { describe, it, expect } from 'vitest';
import { Panier } from './panier';
describe('Panier', () => {
it('a un total de 0 quand il est vide', () => {
const panier = new Panier();
expect(panier.total()).toBe(0);
});
});export class Panier {
total() {
return 0;
}
}Oui, c'est ridicule au début. Mais à l'étape 3 (refactor) puis aux étapes suivantes (ajout, suppression, remise), tu vas construire un Panier solide, fonctionnalité par fonctionnalité, toujours sous filet.
Chapitre 5
Tests d'intégration
On vérifie que plusieurs briques s'emboîtent vraiment : la DB, l'API HTTP, le file system, les services externes (mockés ou non).
Le piège à éviter
Pas de DB de prod, jamais
test_ séparé. Jamais la DB de prod, même en lecture seule.Le pattern fixtures + rollback
import { describe, it, expect, beforeEach } from 'vitest';
import { db } from './db';
import { creerUtilisateur } from './users';
beforeEach(async () => {
await db.user.deleteMany();
});
it('persiste un utilisateur en base', async () => {
const u = await creerUtilisateur({ email: 'alice@exemple.fr' });
const trouve = await db.user.findUnique({ where: { id: u.id } });
expect(trouve?.email).toBe('alice@exemple.fr');
});Variante plus propre : encadrer chaque test par une transaction qui se termine par un ROLLBACK. Tu repars d'un état strictement identique à chaque fois, sans avoir à supprimer manuellement.
Tests d'API HTTP
import { describe, it, expect } from 'vitest';
import request from 'supertest';
import { app } from './app';
it('POST /commandes crée une commande', async () => {
const res = await request(app)
.post('/commandes')
.send({ produitIds: [1, 2] })
.set('Authorization', 'Bearer test-token');
expect(res.status).toBe(201);
expect(res.body).toMatchObject({ id: expect.any(Number) });
});Chapitre 6
End-to-end avec Playwright
Tu simules un·e vrai·e utilisateur·ice dans un vrai navigateur. Lent mais irremplaçable pour les parcours critiques.
Pourquoi Playwright en 2025
- Auto-waiting : Playwright attend que l'élément soit prêt, pas de
sleepmanuel - Locators sémantiques :
getByRole,getByLabel, résistants au refactor UI - Multi-navigateurs : Chrome, Firefox, Safari, en parallèle
- Trace viewer : visualisation pas-à-pas d'un test rouge en CI
- Isolation : chaque test = un contexte navigateur neuf
Exemple : parcours d'inscription
import { test, expect } from '@playwright/test';
test('un·e visiteur·euse peut s\'inscrire', async ({ page }) => {
await page.goto('/signup');
// Locators sémantiques : se basent sur l'accessibilité
await page.getByLabel('Email').fill('test@exemple.fr');
await page.getByLabel('Mot de passe').fill('Secret123!');
await page.getByRole('button', { name: 'Créer mon compte' }).click();
// Auto-waiting : on attend que l'écran de bienvenue apparaisse
await expect(
page.getByRole('heading', { name: 'Bienvenue' }),
).toBeVisible();
});Locators sémantiques par défaut
.btn-primary, #submit-1234). Privilégie getByRole, getByLabel, getByText : si tu changes les classes Tailwind demain, tes tests continuent de passer. Bonus : tu valides en même temps l'accessibilité.Ce qui vaut un test e2e
- Login / signup
- Paiement, processus checkout
- Création d'une ressource critique (commande, ticket, dossier)
- Pas : les variations de couleur, les hovers, les détails CSS (trop fragile)
Chapitre 7
Couverture de code, l'arme à double tranchant
La couverture est un signal utile, mais ce n'est pas un objectif. 100 % de couverture ne signifie pas 0 bug.
Les métriques
- Lines : % de lignes exécutées par les tests (trompeur)
- Branches : % de branches conditionnelles (plus utile)
- Functions : % de fonctions appelées
- Statements : % d'instructions
# Vitest avec couverture
npx vitest run --coverageLe seuil raisonnable
70 à 85 % sur le code métier suffit largement. Pour une lib critique (paiement, crypto), pousse à 95+. Mais ne vise jamais 100 % juste pour le chiffre.
Le piège du 100 %
Mutation testing : la vraie mesure de qualité
Outils comme Stryker : ils modifient ton code volontairement (changent un > en <, par exemple) et vérifient si tes tests le détectent. Si non, tes tests sont faibles. C'est la métrique la plus exigeante.
Chapitre 8
Tests en intégration continue
Si ce n'est pas vert en CI, ce n'est pas mergé. Les tests doivent tourner sur chaque PR, bloquer le merge en cas d'échec.
Le workflow type
name: Tests
on:
push:
branches: [main]
pull_request:
jobs:
unit-integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run test:unit -- --coverage
- run: npm run test:integration
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run test:e2e
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-traces
path: test-results/Stratégie selon la vitesse
- Unit + intégration : bloquants sur chaque PR
- E2E rapide (parcours critiques) : bloquant sur chaque PR
- E2E lent (suite complète) : nightly ou hebdo, avec alerte Slack si rouge
Tests flaky
.skip() permanent : tu perds confiance dans toute la suite.🛠️ Exercice optionnel
Tester un calcul de TVA en TDD
Tu vas implémenter une fonction calculerTVA(montantHT, taux) en TDD : test d'abord, code ensuite. Trois cas couvrent l'essentiel.
Ta mission
La fonction cible :
export function calculerTVA(montantHT: number, taux: number): number {
// À implémenter
}
Trois tests à écrire (dans cet ordre) :
- Cas nominal : 100 € HT à 20 % de TVA renvoie 120
- Arrondi : 33.33 € HT à 20 % renvoie 39.996, arrondi à 40.00
- Erreur : un montant négatif lève une erreur
"Montant négatif"
Méthode :
- Pour chaque test : écris-le, lance-le (il échoue, Red).
- Implémente le minimum dans
calculerTVApour passer au vert. - Refactore si besoin, sans casser les tests précédents.
- Passe au test suivant.
Tu bloques ? Des indices, à dévoiler quand tu en as besoin.
Indice 1
Indice masqué.
Indice 2
Indice masqué.
Indice 3
Indice masqué.
✅ QCM de fin de cours
Teste tes acquis
10 questions, plusieurs réponses parfois possibles. Coche tout ce qui te semble juste, puis valide pour voir ton score et les explications.
- 1
Quel type de test doit être le plus nombreux dans ta suite ?
- 2
Que signifie le pattern AAA ?
- 3
En TDD, par quoi commences-tu ?
- 4
100 % de couverture garantit-il zéro bug ?
- 5
Que doit-on mocker dans un test unitaire ?
- 6
Quel outil e2e est devenu la référence côté JS/TS en 2025 ?
- 7
Un test flaky, c'est quoi ?
- 8
Quel locator Playwright est à privilégier ?
- 9
Tests d'intégration sur la DB : que faire entre chaque test ?
- 10
Le mutation testing sert à ?
Tu peux laisser des questions sans réponse, elles compteront comme fausses.
Tu veux ce cours pour ton équipe ?
Je peux adapter et animer ce cours pour tes formateur·ices ou tes apprenant·es, en présentiel ou en distanciel. Parlons-en pendant l'audit gratuit.
Réserver un audit gratuit →