Yasin Avci

Yasin Avci

Développeur Full-Stack

Paris, France

Me contacter
Retour aux articles
TestingQualitéDevOps

Tests automatisés : la stratégie qui évite les régressions

Tests unitaires, d'intégration, E2E... Comment construire une pyramide de tests efficace sans perdre de temps.

10 avril 20244 min de lecture

"Ça marchait avant la mise en prod." Cette phrase, je l'ai entendue trop souvent. La solution ? Une stratégie de tests adaptée à votre projet.

La pyramide des tests

Concept fondamental : plus un test est haut dans la pyramide, plus il est lent et coûteux à maintenir.

        /\
       /  \     E2E (peu, critiques)
      /----\
     /      \   Intégration (modéré)
    /--------\
   /          \ Unitaires (beaucoup)
  --------------

Tests unitaires : la base solide

Testent une fonction ou un composant isolé, sans dépendances externes.

// utils/formatPrice.test.ts
import { formatPrice } from './formatPrice';

describe('formatPrice', () => {
  it('formate un prix en euros', () => {
    expect(formatPrice(1234.5)).toBe('1 234,50 €');
  });

  it('gère les prix à zéro', () => {
    expect(formatPrice(0)).toBe('0,00 €');
  });

  it('arrondit correctement', () => {
    expect(formatPrice(10.999)).toBe('11,00 €');
  });
});

Caractéristiques :

  • Exécution instantanée (< 10ms par test)
  • Faciles à écrire et maintenir
  • Feedback immédiat pendant le développement

Quoi tester : fonctions utilitaires, logique métier, transformations de données.

Tests d'intégration : les connexions

Vérifient que plusieurs modules fonctionnent ensemble.

// api/users.test.ts
import { createUser, getUser } from './users';
import { db } from './database';

beforeEach(async () => {
  await db.clear();
});

describe('User API', () => {
  it('crée et récupère un utilisateur', async () => {
    const created = await createUser({
      email: 'test@example.com',
      name: 'Test User'
    });

    const retrieved = await getUser(created.id);

    expect(retrieved.email).toBe('test@example.com');
  });
});

Caractéristiques :

  • Plus lents (accès base de données, réseau)
  • Détectent les problèmes d'interface entre modules
  • Nécessitent un environnement de test

Quoi tester : routes API, interactions avec la base de données, services externes mockés.

Tests E2E : le parcours utilisateur

Simulent un utilisateur réel interagissant avec l'application.

// e2e/checkout.spec.ts
import { test, expect } from '@playwright/test';

test('parcours d\'achat complet', async ({ page }) => {
  // Ajouter un produit au panier
  await page.goto('/products/123');
  await page.click('button:text("Ajouter au panier")');

  // Aller au checkout
  await page.click('a:text("Voir le panier")');
  await page.click('button:text("Commander")');

  // Remplir les informations
  await page.fill('[name="email"]', 'client@example.com');
  await page.fill('[name="address"]', '123 Rue Example');
  await page.click('button:text("Payer")');

  // Vérifier la confirmation
  await expect(page.locator('h1')).toContainText('Commande confirmée');
});

Caractéristiques :

  • Lents (plusieurs secondes par test)
  • Fragiles (sensibles aux changements UI)
  • Haute confiance (testent le système complet)

Quoi tester : parcours critiques (inscription, achat, fonctionnalités clés).

Ma stratégie en pratique

Ratio recommandé

| Type | Proportion | Temps d'exécution | |------|------------|-------------------| | Unitaires | 70% | < 30 secondes | | Intégration | 20% | < 2 minutes | | E2E | 10% | < 10 minutes |

Quand écrire des tests ?

  1. Avant de coder (TDD) : pour la logique métier complexe
  2. Après un bug : le test empêche la régression
  3. Pour les parcours critiques : ce qui fait perdre de l'argent si ça casse

Quand NE PAS écrire de tests ?

  • Prototypes jetables
  • Code UI qui change constamment
  • Wrappers triviaux sans logique

Outils recommandés

Pour React/Next.js

{
  "devDependencies": {
    "vitest": "^1.0.0",
    "@testing-library/react": "^14.0.0",
    "playwright": "^1.40.0"
  }
}
  • Vitest : rapide, compatible Jest, watch mode efficace
  • Testing Library : tests orientés comportement utilisateur
  • Playwright : E2E fiable, multi-navigateurs

Structure de fichiers

src/
  components/
    Button/
      Button.tsx
      Button.test.tsx    # Tests unitaires
  lib/
    api/
      users.ts
      users.test.ts      # Tests d'intégration
e2e/
  checkout.spec.ts       # Tests E2E
  auth.spec.ts

Intégration CI/CD

# .github/workflows/test.yml
name: Tests
on: [push, pull_request]

jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install
      - run: pnpm test:unit

  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install
      - run: pnpm build
      - run: pnpm test:e2e

Les tests unitaires bloquent le merge immédiatement. Les E2E tournent en parallèle.

Les pièges à éviter

  1. Tester l'implémentation, pas le comportement

    // ❌ Fragile : teste les détails internes
    expect(component.state.isLoading).toBe(true);
    
    // ✅ Robuste : teste ce que voit l'utilisateur
    expect(screen.getByText('Chargement...')).toBeInTheDocument();
    
  2. Tests trop couplés aux données

    // ❌ Casse si l'ID change
    expect(user.id).toBe(123);
    
    // ✅ Vérifie la structure
    expect(user).toHaveProperty('id');
    
  3. 100% de couverture comme objectif

    La couverture mesure les lignes exécutées, pas la qualité des assertions. 80% de couverture pertinente vaut mieux que 100% de tests superficiels.

Conclusion

Une bonne stratégie de tests n'est pas d'avoir le plus de tests possible, mais les bons tests aux bons endroits.

Commencez petit : quelques tests unitaires sur la logique critique, un test E2E sur le parcours principal. Ajoutez des tests quand vous corrigez des bugs. En quelques mois, vous aurez un filet de sécurité solide.