Le coût des bugs, les types de tests, et les bases des tests unitaires
Bootcode IWA-S04 — Semaine 15, Jour 1
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
Le coût des bugs
Pourquoi les tests sont essentiels en entreprise
Les 3 types de tests
Unitaire, intégration, end-to-end — la pyramide des tests
Un test unitaire concrètement
Le pattern AAA : Arrange, Act, Assert
Le problème : tester avec une BDD
C'est lent et fragile
La solution : l'InMemoryRepository
Tester SANS base de données — c'est instantané !
Module 1
Pourquoi les tests sont essentiels en entreprise
Plus un bug est découvert tard, plus il est coûteux à corriger.
1×
À la conception
Presque gratuit
5×
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×
// 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...
✅ Avec un test unitaire : le bug est détecté en 2 secondes, avant le commit
🛡️ 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
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
Tester UNE fonction, UNE unité de logique, en isolation
✅ Caractéristiques
🎯 On teste quoi ?
// Un test unitaire teste UNE chose, vite
it('additionne 2 + 3', () => {
expect(add(2, 3)).toBe(5);
});
Tester que PLUSIEURS composants fonctionnent ENSEMBLE
✅ Caractéristiques
🎯 On teste quoi ?
⚠️ Plus lent et plus fragile : on en a moins, mais ils sont nécessaires
Simuler le parcours COMPLET d'un utilisateur réel
✅ Caractéristiques
🎯 On teste quoi ?
💡 On en écrit peu : ils valident les parcours critiques, pas chaque fonction
| 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
Le pattern AAA : Arrange, Act, Assert
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);
});
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"
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
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
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);
});
});
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
C'est lent et fragile
🐌 Lent
💔 Fragile
// 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);
});
❌ 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
Tester SANS base de données — c'est instantané !
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 !
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
# 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"
}
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
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 !
❌ 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)
🛡️
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
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 !