Tests en pratique - 00 - Les tests dans un pipeline CI/CD

Intégrer les tests dans GitLab CI. Paralleliser, cacher, échouer vite et afficher les résultats.

00 - Les tests dans un pipeline CI/CD

Ce que tu vas apprendre

  • Pourquoi les tests locaux ne suffisent pas
  • Comment configurer GitLab CI pour lancer bun test
  • Paralleliser, cacher, échouer vite
  • Sauvegarder les rapports de coverage et les screenshots

Prerequisites

Avoir lu la serie Tests fondamentaux, en particulier les articles sur les tests unitaires et d'intégration. Avoir un projet avec GitLab CI configure (voir la serie déploiement GitLab).


"Ca marche sur ma machine"

Je ne compte plus le nombre de fois ou un de mes deux juniors m'a dit "mais ca passe chez moi". Bien sur que ca passe chez toi. Tu as Node 22, Bun 1.2, une base PostgreSQL locale avec les bonnes donnees de seed, et ton .env est configure aux petits oignons depuis 6 mois.

Sauf que la prod, c'est pas ta machine. Et le PC de ton collegue non plus.

Les tests qui tournent uniquement en local ont un problème fondamental : personne ne vérifié qu'ils passent avant de merger. Sur paltemps.fr, avant d'avoir la CI, on avait une regle orale : "lance les tests avant de push". Devinez combien de fois c'etait respecte ? A peu pres une fois sur trois.

Un pipeline CI/CD resout ca. Chaque push déclenché les tests automatiquement. Pas de negociation, pas d'oubli, pas de "je le ferai apres". Le code ne merge pas si les tests echouent.

Le job de test minimal

Voici le strict minimum pour lancer bun test dans GitLab CI :

yamltest:
  stage: test
  image: oven/bun:1
  script:
    - cd api && bun install --frozen-lockfile
    - bun test --coverage
  artifacts:
    when: always
    paths:
      - api/coverage/

Quelques points a noter. --frozen-lockfile empeche bun de modifier le lockfile en CI. Si les dépendances ne matchent pas, le job echoue -- c'est voulu. --coverage généré un rapport de couverture. Et when: always sur les artifacts, c'est pour récupérer le rapport meme si les tests echouent.

Paralleliser les tests

Un seul job qui lance tout, ca marche au début. Quand tu as 400 tests et que le job prend 8 minutes, tu vas vouloir découper. Sur paltemps.fr, on a séparé en trois jobs :

yamlstages:
  - lint
  - test
  - e2e
  - deploy

lint:
  stage: lint
  image: oven/bun:1
  script:
    - cd api && bun install --frozen-lockfile
    - bunx biome check .

test:unit:
  stage: test
  image: oven/bun:1
  script:
    - cd api && bun install --frozen-lockfile
    - bun test --filter '*.test.ts' --bail
  artifacts:
    when: always
    reports:
      junit: api/test-results.xml

test:integration:
  stage: test
  image: oven/bun:1
  services:
    - postgres:16
  variables:
    POSTGRES_DB: test
    POSTGRES_USER: test
    POSTGRES_PASSWORD: test
    DATABASE_URL: "postgresql://test:test@postgres:5432/test"
  script:
    - cd api && bun install --frozen-lockfile
    - bun test --filter '*.integration.test.ts'

test:e2e:
  stage: e2e
  image: mcr.microsoft.com/playwright:v1.50.0
  script:
    - cd api && bun install --frozen-lockfile
    - bunx playwright test
  artifacts:
    when: on_failure
    paths:
      - api/test-results/
      - api/playwright-report/

Les jobs test:unit et test:integration tournent en parallèle (meme stage). Les e2e arrivent apres, parce qu'ils sont plus lents et qu'on veut d'abord s'assurer que les bases fonctionnent.

L'ordre complet du pipeline : lint -> unit tests + intégration tests -> e2e -> deploy. Si le lint echoue, on ne perd pas de temps a lancer les tests.

Cacher les dépendances

Sans cache, chaque job telecharge toutes les dépendances a chaque run. Sur un monolithe avec 200 packages, ca ajoute 30 secondes par job. La solution :

yaml.bun-cache:
  cache:
    key:
      files:
        - api/bun.lockb
    paths:
      - api/node_modules/
      - ~/.bun/install/cache/

test:unit:
  extends: .bun-cache
  # ... reste du job

La clé de cache est basee sur le lockfile. Si les dépendances changent, le cache se régénéré automatiquement. Simon il est réutilisé entre les pipelines.

Échouer vite avec --bail

Quand un test echoue dans la CI, inutile de lancer les 399 tests restants. Le flag --bail arrêté l'exécution au premier échec :

bashbun test --bail

C'est particulièrement utile sur les branches de feature. Tu sais deja que c'est casse, autant economiser les minutes de CI. Sur la branche main par contre, je préféré lancer tous les tests pour avoir une vision complète.

Coverage badge et seuil minimum

Le rapport de coverage c'est bien, mais si personne ne le regarde ca ne sert a rien. Deux astuces :

Première astuce, ajouter un badge de coverage dans le README du projet. GitLab peut parser la sortie de bun test --coverage avec un regex :

yamltest:unit:
  # ...
  coverage: '/All files\s*\|\s*(\d+\.?\d*)\s*\|/'

Deuxieme astuce, faire échouer le job si la coverage tombe en dessous d'un seuil. Pas de 100% (voir l'article 02 sur le piège du coverage), mais un minimum raisonnable :

typescript// bunfig.toml
[test]
coverage = true
coverageThreshold = { line = 60, function = 60 }

Les artifacts qui sauvent la vie

Les artifacts les plus utiles en CI :

  • Rapport de coverage : HTML généré par bun test --coverage, consultable directement dans GitLab
  • Screenshots Playwright : quand un test e2e echoue, le screenshot montre exactement ce que le navigateur affichait
  • Rapport JUnit : GitLab affiche les résultats directement dans la merge request

Le when: on_failure sur les artifacts e2e évité de stocker des screenshots quand tout va bien. On ne garde que ceux des échecs, qui sont les seuls utiles.

Ce que ca change au quotidien

Depuis que le pipeline est en place sur paltemps.fr, les merge requests qui cassent quelque chose sont bloquees avant review. Mes deux juniors ne peuvent plus merger du code qui fait échouer les tests. Ca parait autoritaire dit comme ca, mais en pratique ca les a rassures. Ils savent que s'ils cassent quelque chose, la CI les previent avant que ca arrive en prod.

Le temps total du pipeline est de 3 minutes. C'est assez rapide pour ne pas bloquer le flow de travail, et assez rigoureux pour attraper les regressions.


Article suivant : 01 - Quelle stratégie de test pour quel projet

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.