RousselTMLEARNING

DevOps

CI/CD de bout en bout (2/5) : intégration continue, tests et qualité

Réussir l'intégration continue : pyramide des tests, tests unitaires, d'intégration, de contrat et de bout en bout, lint et analyse statique, couverture, quality gates, tests instables, cache, parallélisation et matrices.

1. La discipline de l'intégration continue #

Plus qu'un serveur de build.

Les règles #

  1. Tout le monde intègre dans la branche principale au moins une fois par jour.
  2. Chaque intégration déclenche un build et des tests automatiques.
  3. Le build est rapide : moins de 10 minutes pour l'étape de commit.
  4. Un build cassé est réparé immédiatement, en priorité sur tout le reste.
  5. Chacun peut voir l'état du build et récupérer le dernier artefact.
À retenir : Avoir Jenkins ou GitHub Actions ne suffit pas : des branches qui vivent trois semaines sans intégration ne font pas de l'intégration continue, même avec un pipeline.

2. Les tests #

Le filet de sécurité de la CI/CD.

La pyramide #

Pyramide des tests
Investir à la base : des tests rapides et fiables donnent un retour en minutes.
TypeVérifieOutils (exemples)
Unitaireune fonction ou une classe isoléepytest, JUnit, Jest, go test
Intégrationle code avec une vraie base, une vraie fileTestcontainers, docker compose
Contratqu'un fournisseur d'API respecte ce qu'attendent ses consommateursPact, schémas OpenAPI
Bout en boutun parcours utilisateur completPlaywright, Cypress
Performancetemps de réponse et tenue en chargek6, Gatling
Sécuritécode, dépendances, imagesSemgrep, Trivy, OWASP ZAP

Tests d'intégration réalistes #

# pytest + Testcontainers : une vraie base PostgreSQL éphémère pour chaque exécution
from testcontainers.postgres import PostgresContainer
import psycopg

def test_insertion_commande():
    with PostgresContainer("postgres:16") as pg:
        with psycopg.connect(pg.get_connection_url().replace("+psycopg2", "")) as conn:
            conn.execute("CREATE TABLE commandes (id serial, total numeric)")
            conn.execute("INSERT INTO commandes (total) VALUES (42)")
            assert conn.execute("SELECT count(*) FROM commandes").fetchone()[0] == 1

Tests instables (flaky) #

Un test qui échoue « parfois » détruit la confiance : l'équipe relance le pipeline sans lire l'erreur, et finit par ignorer un vrai problème.

Bonne Pratique : Mettez un test instable en quarantaine (marqué, non bloquant) avec un ticket, corrigez-le rapidement (attentes explicites, données isolées, horloge simulée) et suivez le taux de relance des pipelines.

3. Qualité et quality gates #

Des règles automatiques, pas des débats en revue.

Analyse statique #

ruff check . && ruff format --check .      # Python : lint et formatage
mypy src/                                  # typage
eslint . && prettier --check .             # JavaScript
hadolint Dockerfile                        # bonnes pratiques Dockerfile
shellcheck scripts/*.sh
gitleaks detect --source .                 # secrets commités

Quality gate #

Exemple de quality gate
Les seuils portent sur le nouveau code, pour ne pas bloquer à cause de l'historique.

Une quality gate est un ensemble de conditions bloquantes (SonarQube, règles de protection de branche). Mesurez la couverture du nouveau code plutôt qu'un pourcentage global : exiger 80 % sur un existant à 30 % bloque tout et pousse à écrire des tests creux.

Erreur Courante : La couverture mesure le code exécuté par les tests, pas la qualité des assertions : un test sans assertion « couvre » du code sans rien vérifier.

4. Un pipeline rapide #

Garder le retour sous les 10 minutes.

Techniques #

TechniqueEffet
Cache des dépendancesévite de retélécharger à chaque build
Parallélisation et découpage des testsdivise la durée par le nombre de runners
Matriceteste plusieurs versions (langage, OS) en parallèle
Exécution sélectivene lancer que ce qui est impacté (monorepo : chemins modifiés)
Cache de couches Dockerreconstruit seulement les couches modifiées
Annulation des builds obsolètesun nouveau push annule le pipeline précédent de la même branche
jobs:
  test:
    strategy:
      matrix:
        python: ["3.11", "3.12", "3.13"]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "${{ matrix.python }}", cache: pip }
      - run: pip install -r requirements.txt && pytest -q -n auto   # pytest-xdist
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

Articles liés

Organiser le DevOps : flux de valeur, équipes et culture sans blâme

Mettre en place une organisation DevOps : chaîne de valeur et value stream mapping, Team Topologies, « you bu…

Git pour le DevOps : branches, revues de code et versions

Organiser le travail avec Git dans une démarche DevOps : trunk-based development ou GitFlow, branches courtes…

CI/CD de bout en bout (1/5) : principes et anatomie d'un pipeline

Comprendre la CI/CD de bout en bout : intégration continue, livraison continue et déploiement continu, étapes…

Pour aller plus loin