1. La discipline de l'intégration continue #
Plus qu'un serveur de build.
Les règles #
- Tout le monde intègre dans la branche principale au moins une fois par jour.
- Chaque intégration déclenche un build et des tests automatiques.
- Le build est rapide : moins de 10 minutes pour l'étape de commit.
- Un build cassé est réparé immédiatement, en priorité sur tout le reste.
- 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 #
| Type | Vérifie | Outils (exemples) |
|---|---|---|
| Unitaire | une fonction ou une classe isolée | pytest, JUnit, Jest, go test |
| Intégration | le code avec une vraie base, une vraie file | Testcontainers, docker compose |
| Contrat | qu'un fournisseur d'API respecte ce qu'attendent ses consommateurs | Pact, schémas OpenAPI |
| Bout en bout | un parcours utilisateur complet | Playwright, Cypress |
| Performance | temps de réponse et tenue en charge | k6, Gatling |
| Sécurité | code, dépendances, images | Semgrep, 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] == 1Tests 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ésQuality gate #
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 #
| Technique | Effet |
|---|---|
| Cache des dépendances | évite de retélécharger à chaque build |
| Parallélisation et découpage des tests | divise la durée par le nombre de runners |
| Matrice | teste plusieurs versions (langage, OS) en parallèle |
| Exécution sélective | ne lancer que ce qui est impacté (monorepo : chemins modifiés) |
| Cache de couches Docker | reconstruit seulement les couches modifiées |
| Annulation des builds obsolètes | un 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