Ce qui arrive à votre code avant qu'il n'atteigne la production

Vous venez de terminer une fonctionnalité. Vous la testez en local, ça marche, vous poussez le code. Et maintenant ?

Entre ce push et le moment où votre code s'exécute en production, il se passe beaucoup de choses. Pas seulement la construction de l'artefact, mais aussi la vérification que le code est réellement sûr, correct et maintenable. Si vous sautez ces vérifications, vous pariez que chaque développeur de l'équipe écrit du code parfait à chaque fois. Personne n'y arrive.

Le processus qui consiste à exécuter des vérifications automatisées à chaque push de code s'appelle l'intégration continue, ou CI. L'idée est simple : détecter les problèmes tôt, quand ils sont peu coûteux à corriger. Un bug trouvé cinq minutes après un push coûte un café. Un bug trouvé en production coûte un rapport d'incident, un rollback et une nuit blanche.

Commencez par les tests unitaires

La première chose que la plupart des pipelines exécutent, ce sont les tests unitaires. Mais ce qui compte comme test unitaire est plus important que ce que la plupart des équipes ne le pensent.

Un bon test unitaire ne teste pas une simple fonction ou méthode juste parce qu'elle existe. Il teste un comportement significatif à partir d'un point d'entrée pertinent. Pour un service backend, ce point d'entrée est généralement un endpoint API ou un cas d'utilisation. Le test appelle cet endpoint et vérifie si la réponse correspond à ce que vous attendez. En coulisses, la requête traverse le contrôleur, la couche service, la logique métier et la limite du repository. Le chemin applicatif interne s'exécute réellement ; les services externes comme les bases de données de production, les files de messages et les API tierces sont remplacés par des doubles de test contrôlés, des instances de test locales, des mocks ou des stubs.

Cette approche a un avantage pratique : vous pouvez modifier l'implémentation interne d'une fonction sans casser le test. Le test se soucie de ce que le système fait, pas de comment il le fait. Cela signifie que vous pouvez refactoriser librement, tant que le comportement reste le même.

Les tests unitaires doivent être rapides. S'ils prennent plus de quelques secondes, quelque chose cloche. Parce qu'ils sont rapides, vous pouvez les exécuter à chaque commit. Cela donne aux développeurs un retour immédiat : "Votre changement a cassé quelque chose, corrigez-le maintenant."

Le linting détecte le style et les mauvaises pratiques

Après les tests unitaires, la vérification suivante est généralement le linting. Le linting ne teste pas la logique. Il vérifie si le code suit des règles de style cohérentes et s'il y a des motifs qui mènent souvent à des bugs.

Un linter détecte des choses comme :

  • Une variable déclarée mais jamais utilisée
  • Une fonction trop longue qui devrait être divisée
  • Une indentation ou un nommage incohérent
  • Des motifs potentiellement dangereux qui semblent corrects mais ne le sont pas

Le linting peut sembler une préoccupation mineure par rapport à la correction, mais il compte plus que vous ne le pensez. Un code qui a l'air cohérent est plus facile à lire. Un code plus facile à lire est plus facile à relire. Un code plus facile à relire a moins de bugs. C'est une chaîne qui commence par une simple vérification automatisée.

Les tests d'intégration vérifient les connexions

Les tests unitaires prouvent que le comportement de l'application fonctionne pendant que les voisins externes sont contrôlés. Les tests d'intégration prouvent que l'application peut fonctionner avec des dépendances réelles.

Pour un service backend, un test d'intégration peut démarrer une vraie base de données de test, lancer le service et vérifier qu'un appel API lit et écrit correctement les données. Ou il peut tester qu'un worker en arrière-plan peut envoyer un message à une file et traiter la réponse.

Les tests d'intégration sont plus lents que les tests unitaires. Ils ont besoin de dépendances réelles : une base de données, une file, peut-être un cache. C'est pourquoi ils s'exécutent après les tests unitaires, pas avant. Si un test unitaire échoue, il est inutile de lancer les tests d'intégration. La modification est déjà cassée.

Certaines équipes exécutent les tests d'intégration dans un environnement de test dédié qui reflète la production aussi fidèlement que possible. D'autres les exécutent dans le pipeline CI en utilisant des conteneurs. Dans les deux cas, l'objectif est le même : détecter les problèmes qui n'apparaissent que lorsque les composants interagissent.

Les scans de sécurité recherchent des vulnérabilités dans votre code

Le scan de sécurité vérifie le code que vous avez écrit pour détecter les vulnérabilités courantes. Comme les injections SQL, le cross-site scripting ou l'utilisation incorrecte de fonctions cryptographiques.

Ces scans sont des outils automatisés qui recherchent des motifs connus comme dangereux. Ils ne sont pas parfaits. Ils peuvent manquer des choses et produire des faux positifs. Mais ils attrapent les fruits les plus accessibles que les relecteurs humains pourraient négliger.

Une vulnérabilité trouvée pendant la CI coûte un ticket et une correction. Une vulnérabilité trouvée en production coûte une brèche, une divulgation et beaucoup de confiance.

Les vérifications de dépendances recherchent des vulnérabilités dans vos bibliothèques

Presque aucun service backend n'est écrit à partir de zéro. Vous utilisez des frameworks, des bibliothèques et des paquets. Chacun d'eux est du code écrit par quelqu'un d'autre, et ce code peut avoir des vulnérabilités.

La vérification des dépendances compare les versions de vos bibliothèques avec les bases de données publiques de vulnérabilités. Si une bibliothèque que vous utilisez a un problème de sécurité connu, la vérification le signale. Certaines équipes configurent le pipeline pour bloquer la construction si une vulnérabilité critique est trouvée. D'autres laissent passer mais notifient l'équipe pour une revue manuelle.

Cette vérification est importante car les vulnérabilités dans les dépendances sont courantes et souvent graves. L'incident Log4j en 2021 en est un exemple bien connu : une bibliothèque de journalisation largement utilisée avait une vulnérabilité permettant l'exécution de code à distance. Les équipes qui avaient une vérification des dépendances dans leur pipeline l'ont découvert rapidement. Les équipes qui ne l'avaient pas ont passé des jours à déterminer ce qu'elles utilisaient.

L'ordre des vérifications est important

La séquence de ces vérifications n'est pas aléatoire. Elle suit une logique pratique :

Le diagramme ci-dessous montre le flux du pipeline et quelles vérifications bloquent la construction.

Voici un workflow GitHub Actions minimal qui impose cet ordre :

name: CI Pipeline

on: [push]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - run: npm run lint

  unit-test:
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - run: npm test -- --coverage

  integration-test:
    needs: unit-test
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
    steps:
      - run: npm run test:integration

  security-scan:
    needs: integration-test
    runs-on: ubuntu-latest
    steps:
      - run: npm run audit

  dependency-check:
    needs: security-scan
    runs-on: ubuntu-latest
    steps:
      - run: npm audit --audit-level=high
flowchart TD A[Push de code] --> B[Tests unitaires] B -->|Succès| C[Linting] B -->|Échec| X[Bloquer le pipeline] C -->|Succès| D[Tests d'intégration] C -->|Échec| X D -->|Succès| E[Scans de sécurité] D -->|Échec| X E -->|Succès| F[Vérifications de dépendances] E -->|Avertissement| F E -->|Échec| X F -->|Succès| G[Construire l'artefact] F -->|Avertissement| G F -->|Échec| X X --> H[Notifier le développeur]
  1. Les tests unitaires et le linting s'exécutent en premier car ils sont rapides. Ils filtrent immédiatement les modifications manifestement cassées ou désordonnées.
  2. Les tests d'intégration s'exécutent ensuite. Ils sont plus lents mais toujours importants. Si les tests unitaires passent mais que les tests d'intégration échouent, vous savez que les composants ne fonctionnent pas ensemble.
  3. Les scans de sécurité et les vérifications de dépendances s'exécutent en dernier. Ils sont souvent les plus lents et moins susceptibles d'échouer sur une modification typique.

Cet ordre donne aux développeurs un retour rapide pour les problèmes courants. Si vous cassez un test unitaire, vous le savez en quelques secondes. Si vous introduisez une dépendance vulnérable, vous le découvrez en quelques minutes, pas en quelques jours.

Toutes les vérifications ne doivent pas bloquer le pipeline

Certaines équipes exécutent toutes les vérifications comme bloquantes : si une vérification échoue, le pipeline s'arrête et l'artefact n'est pas construit. D'autres équipes autorisent certaines vérifications à passer avec des avertissements, en particulier les scans de sécurité qui peuvent produire des faux positifs.

L'important est la cohérence. Chaque modification passe par les mêmes vérifications. La qualité ne dépend pas du fait qu'un développeur se soit souvenu d'exécuter les tests en local. Elle dépend du pipeline.

Une liste de contrôle pratique pour votre pipeline CI

Si vous mettez en place ou révisez un pipeline CI pour un service backend, voici une courte liste de contrôle :

  • Les tests unitaires s'exécutent à chaque commit et se terminent en moins d'une minute
  • Le linting s'exécute avant ou en parallèle des tests unitaires
  • Les tests d'intégration s'exécutent avec des dépendances réelles dans un environnement contrôlé
  • Le scan de sécurité vérifie votre propre code pour les vulnérabilités courantes
  • La vérification des dépendances compare vos bibliothèques avec les bases de données de vulnérabilités connues
  • Le pipeline échoue rapidement : les vérifications rapides s'exécutent en premier, les plus lentes ensuite
  • Chaque modification passe par les mêmes vérifications, quel que soit son auteur

Et ensuite

Une fois toutes ces vérifications passées, l'artefact est construit et prêt. Mais obtenir l'artefact construit n'est que la moitié de l'histoire. La question suivante est de savoir comment mettre cette nouvelle version sur les serveurs sans perturber les utilisateurs. C'est là qu'interviennent les stratégies de déploiement, et c'est ce que nous verrons ensuite.

Pour l'instant, retenez ceci : la qualité de votre système de production est déterminée bien avant qu'il n'atteigne la production. Elle est déterminée dans les minutes qui suivent chaque push, lorsque les vérifications automatisées décident si votre code peut continuer. Faites en sorte que ces vérifications soient rapides, cohérentes et qu'elles s'exécutent à chaque fois.