J 1 — Pourquoi tester ?

Le coût des bugs, les types de tests, et les bases des tests unitaires

Bootcode IWA-S04 — Semaine 15, Jour 1

Objectifs de la leçon

1. Configurer Jest

Dans un projet TypeScript

2. Écrire des tests

Avec describe / it / expect pour des fonctions pures

3. Tester cas normaux ET erreurs

Ne pas se limiter au happy path

4. Lire un rapport Jest

Comprendre les résultats et les échecs

Plan du cours

1

Le coût des bugs

Pourquoi les tests sont essentiels en entreprise

2

Les 3 types de tests

Unitaire, intégration, end-to-end — la pyramide des tests

3

Un test unitaire concrètement

Le pattern AAA : Arrange, Act, Assert

4

Le problème : tester avec une BDD

C'est lent et fragile

5

La solution : l'InMemoryRepository

Tester SANS base de données — c'est instantané !

Module 1

Le coût des bugs

Pourquoi les tests sont essentiels en entreprise

Un bug coûte de plus en plus cher

Plus un bug est découvert tard, plus il est coûteux à corriger.

À la conception

Presque gratuit

Pendant le dev

Débogage local

10×

En test d'intégration

Plusieurs composants

100×

En production

Clients impactés

💡 Les tests unitaires détectent les bugs TÔT, quand ils coûtent 1×

Exemple concret : un bug en production

// Une fonction de calcul de prix

function calculerTotal(prix: number, quantite: number): number {

return prix * quantite; // Bug : pas de TVA!

}

😱 Le bug passe en production...

  • • 10 000 factures incorrectes envoyées aux clients
  • • Service client saturé pendant 3 jours
  • • Correction urgente + remboursements = des jours de travail
  • • Confiance des clients ébranlée

✅ Avec un test unitaire : le bug est détecté en 2 secondes, avant le commit

Les tests : un filet de sécurité

🛡️ Chaque test est une garantie que le code fonctionne

Quand vous modifiez du code, les tests vous disent immédiatement si vous avez cassé quelque chose.

🚀

Confiance

Déployer sans peur

♻️

Refactor

Améliorer le code sereinement

📖

Documentation

Les tests expliquent le code

⚠️ Les tests ne ralentissent pas le dev : ils accélèrent à moyen terme

Module 2

Les 3 types de tests

La pyramide des tests

La pyramide des tests

E2E

Peu, lents, coûteux

Intégration

Moyen nombre, moyens

Unitaires

Beaucoup, rapides, simples

💡 La base de la pyramide est large : beaucoup de tests unitaires, rapides et nombreux

1. Le test unitaire

Tester UNE fonction, UNE unité de logique, en isolation

✅ Caractéristiques

  • • Rapide (< 1 seconde par test)
  • • Pas de base de données
  • • Pas de réseau / serveur
  • • Teste la logique pure

🎯 On teste quoi ?

  • • Une fonction de calcul
  • • Une règle métier
  • • Une validation de données
  • • Un service avec dépendance mockée

// Un test unitaire teste UNE chose, vite

it('additionne 2 + 3', () => {

expect(add(2, 3)).toBe(5);

});

2. Le test d'intégration

Tester que PLUSIEURS composants fonctionnent ENSEMBLE

✅ Caractéristiques

  • • Plus lent (secondes)
  • • Utilise une vraie BDD (test)
  • • Teste les interactions
  • • Vérifie les connexions

🎯 On teste quoi ?

  • • Service + Repository + BDD
  • • API endpoint complet
  • • Requête SQL réelle
  • • Transaction de BDD

⚠️ Plus lent et plus fragile : on en a moins, mais ils sont nécessaires

3. Le test end-to-end (E2E)

Simuler le parcours COMPLET d'un utilisateur réel

✅ Caractéristiques

  • • Très lent (minutes)
  • • Navigateur réel (Playwright)
  • • Environnement complet
  • • Coûteux à maintenir

🎯 On teste quoi ?

  • • "Je me connecte, j'ajoute au panier, je paie"
  • • Parcours utilisateur complet
  • • Frontend + Backend + BDD

💡 On en écrit peu : ils valident les parcours critiques, pas chaque fonction

Comparaison : Unitaires vs Intégration vs E2E

Critère Unitaire Intégration E2E
Vitesse ⚡ Millisecondes ⏱️ Secondes 🐌 Minutes
Quantité Beaucoup (centaines) Moyenne (dizaines) Peu (quelques-uns)
BDD / Réseau ❌ Non ✅ BDD de test ✅ Tout réel
Coût maintenance Faible Moyen Élevé

🎯 Aujourd'hui, on se concentre sur les tests unitaires — la base de la pyramide

Module 3

Un test unitaire concrètement

Le pattern AAA : Arrange, Act, Assert

Le pattern AAA

Chaque test suit la même structure en 3 étapes :

1️⃣

Arrange

Préparer les données et les dépendances

2️⃣

Act

Appeler la fonction à tester

3️⃣

Assert

Vérifier le résultat attendu

it('calcule la TVA', () => {

// 1️⃣ Arrange

const prix = 100;

// 2️⃣ Act

const resultat = calculerTVA(prix);

// 3️⃣ Assert

expect(resultat).toBe(20);

});

1️⃣ Arrange — préparer

Mettre en place tout ce dont le test a besoin

Données d'entrée, dépendances mockées, configuration...

it('calcule la TVA à 20%', () => {

// 1️⃣ Arrange — on prépare les données

const prixHT = 100;

const taux = 0.20;

// ... la suite au slide suivant

});

💡 Bonne pratique : donnez des valeurs explicites et lisibles, pas "magic numbers"

2️⃣ Act — appeler

Exécuter la fonction que l'on teste

Une seule action, le plus simplement possible

it('calcule la TVA à 20%', () => {

// 1️⃣ Arrange

const prixHT = 100;

const taux = 0.20;

// 2️⃣ Act — on appelle la fonction

const resultat = calculerTVA(prixHT, taux);

// ... la suite au slide suivant

});

⚠️ Une seule action par test : si vous appelez 3 fonctions, c'est 3 tests séparés

3️⃣ Assert — vérifier

Vérifier que le résultat correspond à ce qu'on attend

Avec expect() et un "matcher"

it('calcule la TVA à 20%', () => {

// 1️⃣ Arrange

const prixHT = 100;

const taux = 0.20;

// 2️⃣ Act

const resultat = calculerTVA(prixHT, taux);

// 3️⃣ Assert — on vérifie

expect(resultat).toBe(20);

});

toBe()

Égalité stricte

toEqual()

Objets/tableaux

toThrow()

Erreur attendue

describe / it / expect : le vocabulaire Jest

describe

Regroupe les tests liés à une même fonction ou classe

it

Décrit UN cas de test précis ("it should...")

expect

Vérifie une valeur avec un "matcher" (toBe, toEqual...)

describe('calculerTVA', () => {

it('retourne 20 pour 100 HT à 20%', () => {

expect(calculerTVA(100, 0.20)).toBe(20);

});

it('retourne 0 pour prix 0', () => {

expect(calculerTVA(0, 0.20)).toBe(0);

});

});

Tester les cas normaux ET les erreurs

Un bon test ne vérifie pas seulement le "happy path"

✅ Cas normal

Données valides → résultat attendu

❌ Cas d'erreur

Données invalides → erreur attendue

it('lève une erreur si prix négatif', () => {

expect(() => calculerTVA(-50, 0.20))

.toThrow('Le prix doit être positif');

});

💡 Règle : pour chaque fonction, testez au moins 1 cas normal + 1 cas limite + 1 cas d'erreur

Module 4

Le problème : tester avec une BDD

C'est lent et fragile

Pourquoi tester avec une BDD pose problème

🐌 Lent

  • • Connexion à la BDD = millisecondes
  • • Insertion / nettoyage = secondes
  • • 100 tests × 1s = 1 min 40s
  • • On ne lance plus les tests → on perd le filet

💔 Fragile

  • • État partagé entre tests
  • • Données qui persistent → faux échecs
  • • BDD indisponible = tout échoue
  • • Ordre d'exécution compte

// Un test qui dépend de la BDD

it('crée un étudiant', async () => {

await repo.save(etudiant); // ← BDD lente!

const trouve = await repo.findById(id);

expect(trouve).toEqual(etudiant);

});

❌ Test avec BDD vs ✅ Test unitaire pur

❌ Avec BDD (test d'intégration)

it('crée un étudiant', async () => {

const repo = new PgRepository(db);

await repo.save(etudiant);

expect(await repo.findAll()).toHaveLength(1);

});

// ~500ms, dépend de PostgreSQL

✅ Sans BDD (test unitaire)

it('crée un étudiant', () => {

const repo = new InMemoryRepository();

repo.save(etudiant);

expect(repo.findAll()).toHaveLength(1);

});

// ~1ms, aucune dépendance externe

💡 Même logique, même test — mais 500× plus rapide et sans fragilité

Module 5

La solution : l'InMemoryRepository

Tester SANS base de données — c'est instantané !

L'InMemoryRepository : un outil de test

Une implémentation du Repository qui stocke en mémoire (un Map)

Mêmes méthodes que le vrai Repository, mais sans PostgreSQL — parfait pour les tests unitaires

class InMemoryRepository implements Repository {

private data = new Map<string, Entity>();

async save(e: Entity): Promise<void> {

this.data.set(e.id, e);

}

async findById(id: string): Promise<Entity> {

return this.data.get(id);

}

}

💡 Vous l'avez déjà construit en S10 — c'est votre arme secrète pour les tests !

Pourquoi ça marche : dépendre d'une interface

Le service dépend d'une INTERFACE, pas d'une implémentation

// L'interface — le contrat

interface Repository {

save(e: Entity): Promise<void>;

findById(id: string): Promise<Entity>;

}

// Le service utilise l'interface

class EtudiantService {

constructor(private repo: Repository) {}

// ↑ on peut injecter InMemory OU Pg!

}

🧪 En test

InMemoryRepository → rapide, isolé

🚀 En production

PgRepository → données persistées

Configurer Jest en TypeScript

# 1. Installer Jest et ts-jest

npm install --save-dev jest ts-jest @types/jest

# 2. Générer la config Jest

npx ts-jest config:init

// jest.config.js

module.exports = {

preset: 'ts-jest',

testEnvironment: 'node',

testMatch: ['**/*.test.ts'],

};

// package.json — script de test

"scripts": {

"test": "jest",

"test:watch": "jest --watch"

}

Test complet avec InMemoryRepository

import { EtudiantService, InMemoryRepository } from './src';

describe('EtudiantService', () => {

let repo: InMemoryRepository;

let service: EtudiantService;

beforeEach(() => {

repo = new InMemoryRepository();

service = new EtudiantService(repo);

});

it('inscrit un étudiant valide', async () => {

const e = await service.inscrire({ nom: 'Alice' });

expect(e.nom).toBe('Alice');

expect(await repo.findAll()).toHaveLength(1);

});

});

beforeEach : un nouveau repo vide avant chaque test → pas d'état partagé, tests isolés

Lire un rapport Jest

PASS src/etudiant.service.test.ts

EtudiantService

✓ inscrit un étudiant valide (2 ms)

✓ rejette un nom vide (1 ms)

✓ supprime un étudiant existant (3 ms)

Tests: 3 passed, 3 total

Snapshots: 0 total

Time: 0.5 s

✅ PASS (vert)

Le test a réussi — l'assertion est vraie

❌ FAIL (rouge)

Le test a échoué — Jest montre la valeur attendue vs reçue

💡 En cas d'échec, Jest affiche exactement ce qui diffère — lisez-le attentivement !

⚠️ Pièges courants à éviter

❌ Voir les tests comme une corvée

Les tests sont un filet de sécurité, pas une perte de temps — ils vous font gagner du temps à moyen terme

❌ Confondre test unitaire et test d'intégration

Un test unitaire n'utilise NI base de données, NI réseau — sinon c'est un test d'intégration

❌ Penser que les tests ralentissent le dev

C'est le contraire : moins de bugs = moins de débogage = plus de vitesse

❌ Ne tester que le "happy path"

Toujours tester les cas limites (0, négatif, vide) et les cas d'erreur (throw)

À retenir !

🛡️

Tests = filet de sécurité

Détecter les bugs tôt, quand ils coûtent 1×

📐

La pyramide des tests

Beaucoup d'unitaires, moins d'intégration, peu d'E2E

1️⃣2️⃣3️⃣

Le pattern AAA

Arrange → Act → Assert dans chaque test

InMemoryRepository = tests instantanés

Tester la logique sans BDD, grâce aux interfaces

Questions ?

Les tests ne sont pas une corvée — ils sont votre assurance qualité

Prêt à écrire votre premier test unitaire ?

Configurez Jest, écrivez un AAA, et lancez npm test !