Aller au contenu

Objectif du chapitre

Comprendre comment organiser une stratégie de tests automatisés dans un pipeline GitLab CI

  • Identifier ce que chaque niveau de test doit prouver
  • Choisir le bon niveau de test pour éviter une suite lente et fragile
  • Publier les résultats dans GitLab via les rapports de tests et de couverture
  • Garder des exemples simples pour illustrer la théorie

La pyramide des tests

La pyramide des tests aide à répartir les efforts de validation selon le coût d'exécution et de maintenance

  • Tests E2E : Peu nombreux, lents, proches du comportement utilisateur
  • Tests d'intégration : Vérifient la collaboration entre plusieurs composants
  • Tests unitaires : Nombreux, rapides, isolés, exécutés à chaque changement

Idée clé : plus un test est bas dans la pyramide, plus il doit être rapide, stable et fréquent


Ce que la pyramide évite

Sans stratégie claire, une équipe finit souvent avec une suite de tests coûteuse

  • Trop de tests E2E pour vérifier des règles métier simples
  • Des tests unitaires qui mockent tellement qu'ils ne prouvent plus grand-chose
  • Des tests d'intégration instables à cause d'un état partagé
  • Une CI lente que les développeurs contournent ou ignorent

Le but n'est pas d'avoir beaucoup de tests, mais d'avoir les bons tests au bon niveau


Vocabulaire commun

  • Assertion : Vérification du résultat attendu
  • Fixture : Donnée de test préparée avant l'exécution
  • Mock : Objet simulé qui remplace une dépendance
  • Stub : Réponse prédéfinie retournée par une dépendance
  • Flaky test : Test dont le résultat varie sans changement de code

Tests unitaires


Introduction aux tests unitaires

Un test unitaire vérifie le comportement d'une petite unité de code isolée

Caractéristiques

  • Cible une fonction, une méthode ou un composant simple
  • Ne dépend pas d'une base de données, d'un réseau ou d'un service externe
  • Utilise des mocks ou stubs uniquement pour isoler les dépendances
  • S'exécute en quelques millisecondes
  • Donne un feedback immédiat pendant le développement

Outils

  • Vitest ou Jest pour JavaScript et TypeScript
  • Testing Library : Simulation des interactions utilisateur

Structure d'un test unitaire

Un test lisible suit souvent le découpage Arrange / Act / Assert

  • Arrange : préparer les données et dépendances
  • Act : exécuter le comportement testé
  • Assert : vérifier le résultat observable

Un bon test unitaire doit échouer pour une seule raison claire


Exemple de test unitaire

// price.test.ts
import { expect, test } from 'vitest'
import { priceWithTax } from './price'

test('calcule le prix TTC', () => {
  const price = priceWithTax(100, 0.2)

  expect(price).toBe(120)
})

Ce test ne lance ni serveur, ni navigateur, ni base de données


Quand utiliser un mock

Un mock est utile quand la dépendance est lente, instable ou hors du périmètre du test

  • Appel HTTP vers une API externe
  • Envoi d'email
  • Écriture dans une file de messages
  • Lecture d'un secret ou d'une variable d'environnement

À éviter : mocker toutes les fonctions internes de l'application, car le test vérifie alors surtout l'implémentation


Intégration GitLab CI pour les tests unitaires

unit_tests:
  stage: test
  image: node:lts
  script:
    - npm ci
    - npm run test:unit -- --coverage
  artifacts:
    when: always
    paths:
      - coverage/
      - reports/unit.xml
    reports:
      junit: reports/unit.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

Le rapport JUnit affiche les tests dans GitLab, le rapport Cobertura affiche la couverture

Le framework doit être configuré pour générer reports/unit.xml et coverage/cobertura-coverage.xml


Tests d'intégration


Introduction aux tests d'intégration

Un test d'intégration vérifie que plusieurs composants fonctionnent correctement ensemble

Exemples

  • Une API écrit correctement dans une base de données
  • Un service publie un message dans une queue
  • Un module appelle une API interne avec le bon contrat
  • Une couche repository traduit correctement les requêtes métier

Le test d'intégration cherche les erreurs de câblage, de configuration et de contrat


Ce qui change par rapport à l'unitaire

  • Le test accepte certaines dépendances réelles
  • L'environnement est plus lourd à préparer
  • Les données doivent être isolées entre deux tests
  • Le temps d'exécution est plus élevé
  • Le diagnostic est parfois moins immédiat

Le gain principal est la confiance sur les interactions réelles entre composants


Exemple de test d'intégration

// users.integration.test.ts
import request from 'supertest'
import { app } from '../src/app'

test('crée un utilisateur via API', async () => {
  const response = await request(app)
    .post('/users')
    .send({ email: 'ada@example.com' })

  expect(response.status).toBe(201)
  expect(response.body.email).toBe('ada@example.com')
})

Ici, le point d'entrée HTTP est testé avec une partie réelle de l'application


Bases de données en CI

Deux approches courantes

  • Services GitLab CI : GitLab démarre un conteneur à côté du job
  • Testcontainers : Le test démarre lui-même les dépendances Docker nécessaires

Dans les deux cas, il faut prévoir

  • Une base dédiée aux tests
  • Une stratégie de reset entre les tests
  • Des données minimales, explicites et reproductibles

Intégration GitLab CI pour les tests d'intégration

integration_tests:
  stage: test
  image: node:lts
  services:
    - name: postgres:16
      alias: postgres
  variables:
    DATABASE_URL: 'postgres://postgres:postgres@postgres:5432/app_test'
    POSTGRES_PASSWORD: 'postgres'
    POSTGRES_DB: 'app_test'
  script:
    - npm ci
    - npm run test:integration
  artifacts:
    when: always
    reports:
      junit: reports/integration.xml

Le job reste automatisé, mais il teste une dépendance réelle

Le framework doit être configuré pour générer reports/integration.xml


Tests End-to-End


Introduction aux tests E2E

Un test end-to-end valide un parcours utilisateur complet sur une application déployée ou démarrée comme en production

Caractéristiques

  • Traverse le navigateur, le frontend, l'API et la persistance
  • Vérifie les parcours critiques, pas toutes les variantes métier
  • Reproduit des actions utilisateur réelles
  • Produit souvent des captures, traces ou vidéos en cas d'échec
  • Coûte plus cher à exécuter et à maintenir

Un test E2E répond à la question : "le système fonctionne-t-il pour l'utilisateur"


Exemples de parcours E2E utiles

  • Connexion avec un compte valide
  • Création d'une ressource importante
  • Paiement ou validation de commande
  • Inscription et confirmation d'email
  • Parcours de lecture critique pour le métier

Les cas limites et règles fines doivent plutôt rester dans les tests unitaires ou d'intégration


Exemple de test E2E avec Playwright

// auth.spec.ts
import { expect, test } from '@playwright/test'

test('un utilisateur peut se connecter', async ({ page }) => {
  await page.goto('/login')
  await page.getByLabel('Email').fill('user@example.com')
  await page.getByLabel('Password').fill('secret')
  await page.getByRole('button', { name: 'Sign in' }).click()

  await expect(page).toHaveURL('/dashboard')
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible()
})

Les sélecteurs orientés accessibilité rendent le test plus proche du comportement utilisateur


Anti-patterns E2E

  • Utiliser sleep au lieu d'attendre un élément ou un état
  • Tester tous les cas métier via le navigateur
  • Dépendre d'un ordre d'exécution entre tests
  • Réutiliser un état partagé non maîtrisé
  • Cliquer sur des sélecteurs CSS trop liés à l'implémentation

Un test E2E doit rester court, lisible et centré sur un parcours critique


Intégration GitLab CI pour les tests E2E

e2e_tests:
  stage: e2e
  image: mcr.microsoft.com/playwright:v1.60.0-noble
  variables:
    PLAYWRIGHT_JUNIT_OUTPUT_NAME: test-results/e2e.xml
  script:
    - npm ci
    - npx playwright test --reporter=html,junit
  artifacts:
    when: always
    paths:
      - playwright-report/
      - test-results/
    reports:
      junit: test-results/e2e.xml

Les rapports HTML, traces et captures aident à comprendre les échecs en CI


Automatisation dans GitLab CI


Organisation des stages

stages:
  - test
  - e2e

unit_tests:
  stage: test
  script:
    - npm run test:unit

integration_tests:
  stage: test
  script:
    - npm run test:integration

e2e_tests:
  stage: e2e
  script:
    - npm run test:e2e

Les tests rapides peuvent tourner en parallèle dans le même stage, les E2E passent ensuite


Où placer les tests dans le workflow

  • À chaque push : tests unitaires et lint
  • À chaque merge request : unitaires, intégration, couverture, rapports JUnit
  • Avant merge vers main : E2E sur les parcours critiques
  • Après déploiement staging : E2E plus complets sur une application réelle
  • Planifié la nuit : tests longs, cross-browser, charge, suites non bloquantes

Plus le feedback est rapide, plus il est utile au développeur


Gestion des résultats dans GitLab

GitLab peut exploiter les artifacts pour rendre la CI lisible dans l'interface

  • JUnit : liste des tests passés, échoués ou ignorés
  • Cobertura : couverture de code affichée dans les merge requests
  • Artifacts HTML : rapports Playwright, Cypress, coverage HTML
  • Artifacts on failure : logs, captures, traces et vidéos uniquement en cas d'échec

Un job de test doit produire des informations exploitables, pas seulement un statut rouge


Critères de qualité

Une bonne suite de tests automatisés est

  • Rapide : le feedback arrive avant que le contexte soit perdu
  • Déterministe : même code, même résultat
  • Lisible : l'échec indique clairement le comportement cassé
  • Maintenable : le test résiste aux refactorings internes
  • Observable : la CI conserve les rapports utiles au diagnostic

La couverture est un indicateur, pas une preuve de qualité


Récap

  • Les tests unitaires protègent la logique métier rapidement
  • Les tests d'intégration valident les contrats et le câblage entre composants
  • Les tests E2E protègent les parcours utilisateur critiques
  • GitLab CI automatise l'exécution et publie les résultats via les artifacts
  • Une bonne stratégie réduit le coût du feedback sans sacrifier la confiance