Comment mettre en place des quality gates automatiques sur vos PRs GitHub
Merger une pull request sans vérifications automatisées, c'est comme déployer sans tests — ça marche jusqu'au jour où ça ne marche plus. Les quality gates sont les garde-fous automatisés qui s'assurent que chaque PR atteint un niveau minimum avant d'arriver sur votre branche principale. Ils remplacent les approbations subjectives « ça me semble bon » par des vérifications objectives et reproductibles.
Ce guide vous accompagne dans la mise en place de quality gates sur GitHub, des basiques comme le linting et les tests jusqu'aux vérifications avancées au niveau architecture. Que vous partiez de zéro ou que vous cherchiez à améliorer votre pipeline existant, vous y trouverez votre compte.
Qu'est-ce qu'un quality gate ?
Un quality gate est une vérification automatisée qui doit passer avant qu'une pull request puisse être mergée. Si la vérification échoue, la PR est bloquée — sans exception (sauf si vous avez configuré des contournements d'urgence).
Les quality gates répondent à des questions binaires :
- Le code compile-t-il ? Oui ou non.
- Tous les tests passent-ils ? Oui ou non.
- La couverture de tests est-elle au-dessus du seuil ? Oui ou non.
- Y a-t-il des violations de linting ? Oui ou non.
- Cette PR introduit-elle des violations architecturales ? Oui ou non.
La puissance des quality gates réside dans leur caractère non négociable. Contrairement aux commentaires de revue de code qui peuvent être ignorés ou reportés, un quality gate en échec empêche physiquement le merge. Cela crée un plancher de qualité cohérent que toute l'équipe respecte.
Pourquoi automatiser plutôt que se fier à la revue de code ?
La revue de code a de la valeur, mais elle a ses limites :
- Les reviewers sont humains : Ils ratent des choses, surtout sous pression de temps. Un reviewer pourrait repérer un bug logique mais manquer une dépendance circulaire subtile.
- Inconsistance : Différents reviewers ont différents standards. Ce qu'un reviewer signale, un autre pourrait l'approuver.
- Fatigue : Dans les équipes avec un volume élevé de PRs, la qualité des revues se dégrade quand les reviewers sont débordés.
- Portée : Les reviewers se concentrent sur le diff. Ils vérifient rarement comment les changements d'une PR affectent la structure globale de la codebase.
Les quality gates automatisés gèrent les vérifications répétables et objectives. Cela libère les reviewers pour se concentrer sur ce que les humains font le mieux : évaluer les décisions de conception, les choix de nommage et la justesse de la logique métier.
Niveau 1 : Quality gates basiques
C'est le minimum. Chaque projet devrait avoir ces vérifications sur chaque PR.
Linting
Un linter repère les problèmes de style, les bugs potentiels et les mauvaises pratiques. Configurez-le comme une GitHub Action :
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm lint
Pour les projets JavaScript/TypeScript, utilisez Biome (rapide, remplace ESLint + Prettier) ou ESLint avec une configuration stricte. L'essentiel est que le linter applique les mêmes règles en local et en CI — pas de surprise au push.
Vérification de types
Si vous utilisez TypeScript, exécutez le vérificateur de types en CI. Il attrape des erreurs que le linting ne voit pas :
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm tsc --noEmit
Cela garantit que personne ne merge du code avec des erreurs de type, même si leur IDE local ne les a pas détectées.
Tests
Exécutez votre suite de tests sur chaque PR. Si un test échoue, la PR est bloquée :
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm test
Pour les projets avec des suites de tests lentes, envisagez d'exécuter les tests unitaires sur chaque PR et les tests E2E uniquement sur les merges vers main (ou de nuit).
Vérification du build
Un build réussi est le test d'intégration ultime. S'il échoue, quelque chose est cassé :
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm build
Rendre les status checks obligatoires
Avoir ces jobs qui tournent ne suffit pas — il faut les rendre obligatoires. Dans GitHub :
- Allez dans Settings → Branches → Branch protection rules
- Cliquez sur Add rule (ou modifiez la règle existante pour
main) - Activez Require status checks to pass before merging
- Recherchez et sélectionnez vos jobs CI :
lint,type-check,test,build - Activez Require branches to be up to date before merging (empêche les conflits de merge de causer des échecs)
Maintenant, aucune PR ne peut être mergée sauf si les quatre vérifications passent. Le bouton « Merge » est grisé tant qu'elles ne sont pas vertes.
Niveau 2 : Gates de couverture et de taille
Une fois les bases en place, ajoutez des vérifications qui suivent les tendances de qualité.
Seuils de couverture de tests
Des outils comme Vitest, Jest ou Codecov peuvent rapporter la couverture et échouer si elle passe sous un seuil :
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm test -- --coverage
- uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true
Configurez Codecov (ou votre outil de couverture) pour faire échouer la PR si :
- La couverture globale passe sous un seuil (par ex. 70 %)
- Le nouveau code a moins de 80 % de couverture
Cela empêche l'érosion progressive de la couverture de tests qui survient quand « juste cette PR-ci » saute les tests.
Vérifications de taille de bundle
Pour les projets frontend, la taille du bundle impacte directement l'expérience utilisateur. Suivez-la :
bundle-size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm build
- uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
size-limit commente chaque PR avec le changement de taille du bundle. Vous pouvez le configurer pour échouer si le bundle dépasse un seuil.
Limites de taille des PRs
Les grosses PRs sont plus difficiles à revoir et plus susceptibles de contenir des problèmes cachés. Bien qu'il soit difficile de bloquer les grosses PRs avec une GitHub Action, vous pouvez ajouter un avertissement :
pr-size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Vérifier la taille de la PR
run: |
CHANGED=$(git diff --numstat origin/main...HEAD | wc -l)
if [ "$CHANGED" -gt 30 ]; then
echo "::warning::Cette PR touche $CHANGED fichiers. Envisagez de la découper."
fi
Niveau 3 : Propriété du code et exigences de revue
GitHub fournit des fonctionnalités intégrées pour contrôler qui peut approuver quoi.
CODEOWNERS
Le fichier CODEOWNERS associe des chemins aux reviewers obligatoires :
# .github/CODEOWNERS
# Propriétaires globaux (fallback)
* @tech-lead
# Frontend
/src/components/ @frontend-team
/src/app/ @frontend-team
# Backend
/src/server/ @backend-team
/src/lib/db/ @backend-team @tech-lead
# Infrastructure
/.github/ @devops-team
/docker/ @devops-team
# Facturation (sensible)
/src/lib/stripe/ @tech-lead @billing-team
/src/server/actions/billing* @tech-lead @billing-team
Combiné avec Require review from Code Owners dans les règles de protection de branche, cela garantit que les changements au code de facturation sont toujours revus par l'équipe facturation, les changements d'infrastructure par l'équipe devops, etc.
Approbations requises
Dans les paramètres de protection de branche, vous pouvez exiger un nombre minimum d'approbations (généralement 1 ou 2) avant le merge. Combiné avec CODEOWNERS, cela crée un processus de revue robuste :
- Au moins une approbation requise
- Les bonnes personnes doivent approuver les changements dans leurs domaines
- Les revues périmées sont annulées quand de nouveaux commits sont poussés
Niveau 4 : Quality gates architecturaux
C'est là que la plupart des équipes s'arrêtent. Mais les quality gates les plus impactants opèrent au niveau de l'architecture — vérifiant non seulement si le code est correct, mais s'il respecte la conception structurelle de la codebase.
Empêcher les dépendances circulaires
Les dépendances circulaires sont l'un des problèmes architecturaux les plus courants. Elles rendent le code plus difficile à comprendre, tester et refactorer. Vous pouvez les attraper en CI.
Avec dependency-cruiser, ajoutez une règle qui échoue sur les dépendances circulaires :
// .dependency-cruiser.cjs
module.exports = {
forbidden: [
{
name: "no-circular",
severity: "error",
from: {},
to: {
circular: true
}
}
]
};
Puis ajoutez-le à votre pipeline CI :
dependency-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: npx depcruise --config .dependency-cruiser.cjs src
Appliquer les frontières de modules
Au-delà des dépendances circulaires, vous pouvez imposer que certains modules ne dépendent pas d'autres. Par exemple, votre couche données ne devrait jamais importer de votre couche présentation :
{
"forbidden": [
{
"name": "no-data-to-ui",
"comment": "La couche données ne doit pas dépendre de la couche présentation",
"severity": "error",
"from": { "path": "^src/data" },
"to": { "path": "^src/(components|app)" }
},
{
"name": "no-ui-to-db",
"comment": "Les composants UI ne doivent pas accéder directement à la base de données",
"severity": "error",
"from": { "path": "^src/(components|app)" },
"to": { "path": "^src/lib/db" }
}
]
}
Vérifications d'architecture comme status checks GitHub
Des outils comme ReposLens s'intègrent directement avec GitHub comme status check. Quand une PR est ouverte, ReposLens analyse les changements et rapporte :
- Si de nouvelles dépendances circulaires ont été introduites
- Si le couplage des modules a augmenté
- Si des frontières architecturales ont été franchies
- Un diff visuel du graphe de dépendances (avant vs. après)
Cela apparaît comme un status check sur la PR — vert si l'architecture est respectée, rouge si elle est violée. Combiné avec les règles de protection de branche exigeant que ce check passe, cela empêche la dégradation architecturale à la source.
L'avantage par rapport aux configurations DIY avec dependency-cruiser est que ReposLens ne nécessite aucun fichier de configuration. Il infère l'architecture à partir de votre code et signale les violations automatiquement. Vous pouvez ensuite ajouter des règles explicites si vous voulez un contrôle plus strict.
Contournements d'urgence
Aucun système ne devrait être impossible à contourner en cas d'urgence réelle. GitHub fournit deux mécanismes :
Contournement pour les administrateurs
Dans les paramètres de protection de branche, vous pouvez autoriser les administrateurs à contourner les status checks requis. Cela devrait être utilisé avec parcimonie — uniquement pour les hotfixes de production ou quand un check est véritablement cassé.
Listes de contournement des rulesets
La fonctionnalité plus récente des rulesets de GitHub (remplaçant les anciennes règles de protection de branche) vous permet de créer des listes de contournement pour des équipes ou utilisateurs spécifiques. C'est plus granulaire que le contournement admin.
L'essentiel est la traçabilité. Si quelqu'un contourne un quality gate, l'équipe devrait le savoir et revoir manuellement les vérifications sautées.
Bonnes pratiques pour les quality gates
Échouer vite
Ordonnez vos vérifications de la plus rapide à la plus lente. Si le linting échoue en 10 secondes, il n'y a aucune raison d'attendre 5 minutes que la suite de tests tourne :
jobs:
lint:
# Tourne en ~15 secondes
type-check:
# Tourne en ~30 secondes
test:
needs: [lint, type-check]
# Tourne en ~3 minutes, seulement si lint et type-check passent
build:
needs: [test]
# Tourne en ~2 minutes, seulement si les tests passent
Utiliser needs garantit que les jobs lents ne démarrent pas tant que les jobs rapides n'ont pas passé. Cela économise des minutes CI et donne aux développeurs un feedback plus rapide.
Fournir des messages d'erreur clairs
Quand un quality gate échoue, le développeur doit savoir exactement quoi corriger. Les mauvais messages d'erreur causent frustration et perte de temps.
Au lieu de :
Error: Quality check failed
Fournissez :
Error: Dépendance circulaire détectée
src/modules/billing/service.ts
→ src/modules/projects/queries.ts
→ src/modules/billing/utils.ts (cycle)
Correction : Déplacez la logique partagée dans src/shared/ ou utilisez un pattern basé sur les événements.
La plupart des linters et frameworks de tests font cela bien nativement. Pour les vérifications personnalisées, investissez dans de bons messages d'erreur.
Garder les vérifications rapides
Si vos quality gates prennent 20 minutes à tourner, les développeurs changeront de contexte en attendant — et le changement de contexte tue la productivité. Visez :
- Linting : moins de 30 secondes
- Vérification de types : moins de 1 minute
- Tests unitaires : moins de 3 minutes
- Build : moins de 3 minutes
- Tests E2E : moins de 10 minutes (en parallèle)
Utilisez le cache agressivement. Le cache GitHub Actions pour node_modules, les artefacts de build et les bases de données de test peut réduire les temps CI de manière spectaculaire.
Ne pas bloquer sur les avertissements
Il y a une tentation d'ajouter des quality gates « souples » qui avertissent mais ne bloquent pas. Résistez à cette tentation pour les vérifications importantes. Les avertissements sont ignorés. Si quelque chose est assez important pour être vérifié, faites-en un échec bloquant.
Réservez les avertissements pour les métriques informatives comme les changements de taille de bundle ou la taille des PRs — des choses utiles à savoir mais qui n'ont pas de seuil clair de réussite/échec.
Versionner la configuration des quality gates
Toutes les configurations de quality gates devraient vivre dans le dépôt :
- Configuration du linter (
.eslintrc,biome.json) - Configuration des tests (
vitest.config.ts,jest.config.ts) - Seuils de couverture
- Règles de dépendances (
.dependency-cruiser.cjs) - Workflows GitHub Actions (
.github/workflows/) - Fichier CODEOWNERS
Cela garantit que les quality gates évoluent avec le code et que chaque branche utilise les règles appropriées.
Tout assembler
Voici un workflow CI complet qui implémente tous les niveaux discutés :
name: PR Quality Gates
on:
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm lint
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm tsc --noEmit
test:
runs-on: ubuntu-latest
needs: [lint, type-check]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm test -- --coverage
- uses: codecov/codecov-action@v4
build:
runs-on: ubuntu-latest
needs: [test]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm build
dependency-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: npx depcruise --config .dependency-cruiser.cjs src
Ajoutez ReposLens comme GitHub App pour la vérification au niveau architecture (aucune configuration de workflow nécessaire — il s'exécute automatiquement sur les PRs une fois installé).
Puis configurez la protection de branche pour exiger que toutes ces vérifications passent. Le résultat est un système de quality gates multicouche qui attrape les problèmes à chaque niveau — du style et des types aux tests et à l'architecture.
Commencer petit
Si votre projet n'a actuellement aucun quality gate, n'essayez pas de tout implémenter d'un coup. Commencez par :
- Semaine 1 : Ajoutez le linting et la vérification de types comme checks obligatoires
- Semaine 2 : Ajoutez la suite de tests comme check obligatoire
- Semaine 3 : Ajoutez la vérification du build
- Semaine 4 : Configurez CODEOWNERS et les revues obligatoires
- Mois 2 : Ajoutez les seuils de couverture et les vérifications de dépendances
- Mois 3 : Ajoutez les vérifications au niveau architecture
Chaque étape vous apporte de la valeur immédiate et construit les fondations pour la suivante. En trois mois, vous aurez un système complet de quality gates qui attrape la plupart des problèmes avant qu'ils n'atteignent votre branche principale.
L'objectif n'est pas la perfection — c'est un plancher de qualité cohérent et automatisé sur lequel toute l'équipe peut compter. Chaque vérification que vous ajoutez réduit la charge sur les reviewers humains et augmente la fiabilité de votre codebase.