DevOps · C3 Intégration et livraison continues · Chapitre 1 · 7 h
Intégration continue
Principe et discipline associée ; pipeline de compilation, tests et analyse statique ; déclencheurs, jobs, artefacts et cache ; échec rapide et boucle de rétroaction courte.
Deux développeurs travaillent trois semaines chacun de leur côté. Le jour de l'intégration arrive, on fusionne, et rien ne compile. Les deux ont modifié la même interface, chacun de façon cohérente avec son propre travail, et il faut maintenant décider laquelle survit — puis corriger tout ce qui en dépendait des deux côtés.
Cette journée porte un nom dans les équipes qui la vivent : merge day. Elle prend un jour, ou trois. Et sa cause n'est pas technique : les deux modifications étaient compatibles il y a trois semaines, et elles ont divergé sans que personne ne s'en aperçoive.
L'intégration continue est la réponse, et elle n'est pas un outil : c'est la discipline d'intégrer son travail dans la branche principale au moins une fois par jour, en s'assurant à chaque fois, automatiquement, que l'ensemble tient encore debout.
Le principe, et la discipline qui va avec
L'outil — GitHub Actions, GitLab CI, Jenkins — ne fait qu'exécuter des commandes après un
push. Ce qui produit le bénéfice, ce sont quatre règles de comportement.
Intégrer au moins une fois par jour. C'est le trunk-based du chapitre 2. Une branche de trois semaines rend le pipeline inutile : il vérifiera fidèlement une divergence déjà constituée.
Chaque intégration déclenche une vérification automatique. Compilation, tests, analyse statique. Automatique, parce qu'une vérification manuelle est une vérification qu'on saute un vendredi soir.
Une construction cassée s'arrête et se répare tout de suite. C'est la règle qui coûte le plus à installer, et celle qui fait toute la différence. Tant que la branche principale est rouge, personne ne peut savoir si son propre travail est correct — la vérification ne dit plus rien. Réparer passe donc avant d'ajouter.
Personne ne construit par-dessus du rouge. Corollaire du précédent, et il est social autant que technique.
Sans ces règles, on obtient ce qu'on voit dans beaucoup de projets : un pipeline qui existe, qui échoue depuis trois semaines, et que tout le monde a appris à ignorer. Il coûte du temps de calcul et ne rend plus aucun service.
Un pipeline
name: intégrationon: push: branches: [main] pull_request: jobs: verifier: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' # dépendances mises en cache - run: npm ci - run: npm run lint # rapide : en premier - run: npm run test:unit - run: npm run test:integration - uses: actions/upload-artifact@v4 with: name: rapport-couverture path: coverage/Cinq notions se lisent dans ce fichier, et elles sont communes à tous les outils.
Le déclencheur dit quand le pipeline part : à chaque push sur la branche principale, à
chaque demande de fusion, sur une étiquette de version — c'est le chapitre 6 — ou selon un
horaire, pour les vérifications lentes qu'on ne veut pas à chaque commit.
Un job est une unité qui s'exécute sur une machine propre. Deux jobs indépendants tournent en parallèle ; c'est le levier principal pour raccourcir le pipeline.
Une étape est une commande dans un job, exécutée dans l'ordre. La première qui échoue arrête le job.
Un artefact est un fichier produit par un job et conservé : rapport de couverture, image construite, binaire. C'est aussi le moyen de passer un résultat d'un job à un autre.
Le cache conserve entre deux exécutions ce qui est coûteux à reconstituer — dépendances, couches Docker du chapitre 3. Sans lui, chaque exécution repart d'une machine vierge.
L'échec rapide, et la durée du cycle
Le pipeline doit répondre vite, pour une raison qui n'a rien de technique : au-delà d'une dizaine de minutes, on ne l'attend plus. On passe à autre chose, on perd le contexte, et le retour arrive quand on pense à autre chose — la boucle de rétroaction du chapitre 1 est cassée.
Deux leviers, et le premier est gratuit.
Ordonner du plus rapide au plus lent. Le formatage et l'analyse statique prennent dix secondes et attrapent les fautes les plus fréquentes ; les tests unitaires prennent une minute ; les tests d'intégration cinq ; les tests de bout en bout quinze. Dans cet ordre, une faute de lint est signalée en dix secondes au lieu de vingt minutes.
Paralléliser ce qui est indépendant. Les tests unitaires et l'analyse statique n'ont aucune raison d'attendre l'un l'autre. Trois jobs parallèles de cinq minutes valent mieux qu'un job séquentiel de quinze.
séquentiel [lint 10s][unitaires 60s][intégration 300s][e2e 900s] = 21 minparallèle [lint 10s] [unitaires 60s] puis [intégration 300s] [analyse sécurité 120s] [e2e 900s] = 22 min… ?Le second schéma illustre une erreur courante : paralléliser ce qui est déjà rapide ne gagne rien. Le pipeline dure aussi longtemps que son chemin le plus long — c'est le chemin critique du chapitre 4 d'architecture. Pour descendre sous dix minutes, il faut s'attaquer aux tests de bout en bout : les découper, les paralléliser entre eux, ou les sortir du pipeline de chaque commit pour les exécuter à la nuit.
Qualité de code et couverture
Trois outils s'ajoutent au pipeline, et le troisième demande une mise en garde.
Le formateur — Prettier, gofmt, black — met le code en forme automatiquement. Le mettre
dans le pipeline supprime définitivement les discussions d'indentation en revue, ce qui n'est
pas un gain esthétique mais un gain d'attention : le relecteur regarde la logique.
L'analyse statique — ESLint, SonarQube — cherche des motifs suspects sans exécuter le code :
variable inutilisée, comparaison toujours vraie, ressource non fermée. C'est le -Wall du cours
de programmation, appliqué au projet.
La couverture de tests mesure la proportion de lignes exécutées par la suite de tests. Elle est utile comme signal — une couverture de 5 % dit quelque chose — et dangereuse comme objectif. Dès qu'on impose un seuil, on l'atteint : il suffit d'écrire des tests qui appellent le code sans rien vérifier. On obtient 90 % de couverture et aucune garantie, ce qui est pire que 40 % assumés, parce que le chiffre inspire une confiance injustifiée.
C'est un cas particulier d'une règle générale, qu'il vaut mieux connaître : quand une mesure devient un objectif, elle cesse d'être une bonne mesure. On surveille donc la tendance de la couverture plutôt que sa valeur, et l'on regarde surtout ce qui n'est pas couvert du tout.
Quiz · 1 question
Un pipeline exécute, dans cet ordre : tests de bout en bout (15 min), tests unitaires (1 min), analyse statique (10 s). Que faut-il changer, et pourquoi ?
- Rien : l'ordre n'a pas d'importance puisque toutes les étapes s'exécutent de toute façon — sans importance
- Inverser l'ordre : du plus RAPIDE au plus lent. Une faute de lint serait alors signalée en 10 secondes au lieu de 16 minutes — et la première étape qui échoue arrête le job — du plus rapide au plus lent
- Supprimer l'analyse statique, redondante avec les tests unitaires — supprimer une étape
Réponse : La première étape qui échoue arrête le job : l'ordre décide donc du délai avant le retour d'information. Avec l'ordre donné, une variable mal nommée ou une accolade oubliée n'est signalée qu'après seize minutes de tests de bout en bout — et pendant ce temps l'auteur a changé de tâche, perdu le contexte, et devra y revenir. En inversant, le même défaut remonte en dix secondes, tant que le code est encore sous les yeux. C'est le principe de l'ÉCHEC RAPIDE, et il ne coûte rien à mettre en place : trois lignes déplacées. L'analyse statique n'est par ailleurs pas redondante avec les tests — elle cherche des motifs suspects SANS exécuter, et attrape des choses qu'aucun test ne couvre, comme une ressource non fermée sur un chemin d'erreur rare.
Ce qui tue une intégration continue
Trois maux, et le premier est de loin le pire.
Les tests instables (flaky), qui échouent une fois sur vingt sans raison. Leur effet est catastrophique et le calcul est instructif : si chaque test échoue au hasard avec une probabilité , la probabilité qu'au moins un des tests échoue vaut . Avec et 200 tests, cela fait 63 % d'échecs sans aucune cause réelle.
La conséquence est prévisible : l'équipe relance le pipeline « pour voir », et ce faisant apprend à ignorer les échecs. Un vrai échec passe alors inaperçu. Un test instable doit être réparé ou supprimé le jour même — le garder est pire que ne pas l'avoir.
Les pipelines lents. Au-delà de dix minutes, on n'attend plus ; au-delà de trente, on regroupe ses commits pour ne pas payer l'attente, et l'on retombe sur les gros lots du chapitre 1.
Les tests dépendants entre eux. Un test qui suppose qu'un autre a rempli la base ne passe que dans un ordre donné, et devient instable dès qu'on parallélise. Chaque test doit créer et détruire ses propres données.
Quiz · 1 question
Une équipe impose un seuil de 90 % de couverture de tests. Quel est le risque ?
- Aucun : 90 % est un objectif exigeant qui garantit une bonne qualité — aucun risque
- Le seuil sera atteint, mais pas forcément par de bons tests : il suffit d'appeler le code sans rien vérifier. On obtient 90 % et aucune garantie — pire que 40 % assumés, car le chiffre inspire une confiance injustifiée — la mesure devenue objectif
- Le pipeline deviendra trop lent pour être exécuté à chaque commit — lenteur
Réponse : La couverture mesure les lignes EXÉCUTÉES par les tests, pas les comportements VÉRIFIÉS. Un test qui appelle toutes les méthodes sans écrire une seule assertion donne 100 % de couverture et ne détecte rien. Dès qu'un seuil devient contraignant, c'est cette solution que la pression fait émerger — pas par malhonnêteté, mais parce que c'est le chemin le plus court vers le chiffre demandé. Le résultat est plus dangereux qu'une couverture faible assumée, puisque le tableau de bord affiche du vert. C'est un cas particulier d'une règle générale : quand une mesure devient un objectif, elle cesse d'être une bonne mesure. On surveille donc la TENDANCE plutôt que la valeur, et l'on regarde surtout ce qui n'est couvert PAR AUCUN test — un module entier à 0 % est une information, la différence entre 78 % et 82 % n'en est pas une.
À vous
L'exercice construit un exécuteur de pipeline et met en chiffres les trois affirmations du chapitre.
D'abord l'ordonnancement : vous donnez des étapes avec leur durée et leur probabilité d'échec, et le programme calcule le délai moyen avant retour selon l'ordre choisi. L'échec rapide cesse d'être un conseil pour devenir un écart mesuré.
Ensuite le cache et le parallélisme : mêmes étapes, avec et sans cache de dépendances, en séquentiel puis réparties en jobs. Vous chercherez le chemin critique et constaterez que paralléliser ce qui est déjà rapide ne gagne rien.
Enfin les tests instables. Vous ferez varier la probabilité d'instabilité et le nombre de tests, et vous retrouverez les 63 % du cours — puis vous simulerez ce que fait une équipe qui relance « pour voir », et combien de vrais échecs elle laisse passer.
Exercice de code
Mesurez l'échec rapide, trouvez le chemin critique, puis chiffrez le coût des tests instables.
Point de départ
// ── 1. Ordre des étapes et délai avant retour ─────────────────────────────
// duree en secondes, proba = probabilité que CETTE étape révèle un défaut.
const ETAPES = [
{ nom: "lint", duree: 10, proba: 0.25 },
{ nom: "tests unitaires", duree: 60, proba: 0.35 },
{ nom: "intégration", duree: 300, proba: 0.20 },
{ nom: "bout en bout", duree: 900, proba: 0.10 },
];
// Le job s'arrête à la PREMIÈRE étape qui échoue : le délai avant retour
// dépend donc de l'ordre.
function delaiMoyen(etapes) {
let cumul = 0, esperance = 0, resteVrai = 1;
for (const e of etapes) {
cumul += e.duree;
// ← à écrire : probabilité d'arriver jusqu'ici ET d'échouer ici,
// pondérée par le temps écoulé
}
// Cas où tout passe : on paie la totalité.
esperance += resteVrai * cumul;
return esperance;
}
// ── 2. Cache et parallélisme ──────────────────────────────────────────────
const AVEC_CACHE = { installation: 8 };
const SANS_CACHE = { installation: 95 };
// Des jobs, chacun avec ses étapes et ses dépendances.
const JOBS = [
{ nom: "lint", duree: 10, depend: ["installation"] },
{ nom: "unitaires", duree: 60, depend: ["installation"] },
{ nom: "sécurité", duree: 120, depend: ["installation"] },
{ nom: "intégration", duree: 300, depend: ["unitaires"] },
{ nom: "bout en bout", duree: 900, depend: ["intégration"] },
];
function cheminCritique(jobs, installation) {
const fin = { installation };
// ← à écrire : la fin d'un job est le max des fins de ses dépendances,
// plus sa propre durée. Le pipeline dure jusqu'à la fin la plus tardive.
return 0;
}
// ── 3. Tests instables ────────────────────────────────────────────────────
function probaEchecPipeline(nbTests, instabilite) {
return 0; // ← à écrire : 1 − (1 − p)^n
}
// ── À VOUS ────────────────────────────────────────────────────────────────
// 1. Écrivez delaiMoyen, puis comparez l'ordre rapide→lent et lent→rapide.
// 2. Écrivez cheminCritique. Que gagne le cache ? Et si l'on parallélise
// seulement les étapes déjà rapides ?
// 3. Retrouvez les 63 % du cours, puis simulez une équipe qui relance
// « pour voir » : combien de VRAIS échecs passent inaperçus ?
console.log(" délai moyen :", delaiMoyen(ETAPES).toFixed(0), "s");
Solution
const ETAPES = [
{ nom: "lint", duree: 10, proba: 0.25 },
{ nom: "tests unitaires", duree: 60, proba: 0.35 },
{ nom: "intégration", duree: 300, proba: 0.20 },
{ nom: "bout en bout", duree: 900, proba: 0.10 },
];
function delaiMoyen(etapes) {
let cumul = 0, esperance = 0, resteVrai = 1;
for (const e of etapes) {
cumul += e.duree;
// Espérance : (probabilité d'arriver ici sans avoir échoué avant)
// × (probabilité d'échouer ici) × (temps écoulé à cet instant).
esperance += resteVrai * e.proba * cumul;
resteVrai *= 1 - e.proba;
}
esperance += resteVrai * cumul; // tout passe : on paie la totalité
return esperance;
}
console.log("— 1. l'échec rapide, en secondes —");
const rapideDabord = [...ETAPES];
const lentDabord = [...ETAPES].reverse();
for (const [nom, ordre] of [["rapide → lent", rapideDabord], ["lent → rapide", lentDabord]]) {
console.log(" " + nom.padEnd(16) + ordre.map((e) => e.nom).join(" → "));
console.log(" délai moyen avant retour : " + Math.round(delaiMoyen(ordre)) + " s");
}
const gain = delaiMoyen(lentDabord) - delaiMoyen(rapideDabord);
console.log(" Écart : " + Math.round(gain) + " s en moyenne, pour TROIS LIGNES déplacées.");
console.log(" Et le pire cas compte autant : une faute de lint remonte en 10 s");
console.log(" au lieu de 1270 s — tant que le code est encore sous les yeux.");
console.log("");
console.log("— 2. cache et chemin critique —");
const JOBS = [
{ nom: "lint", duree: 10, depend: ["installation"] },
{ nom: "unitaires", duree: 60, depend: ["installation"] },
{ nom: "sécurité", duree: 120, depend: ["installation"] },
{ nom: "intégration", duree: 300, depend: ["unitaires"] },
{ nom: "bout en bout", duree: 900, depend: ["intégration"] },
];
function cheminCritique(jobs, installation) {
const fin = { installation };
let reste = [...jobs];
while (reste.length) {
const pret = reste.filter((j) => j.depend.every((d) => fin[d] !== undefined));
if (!pret.length) throw new Error("dépendance circulaire");
for (const j of pret) {
// Un job démarre quand TOUTES ses dépendances sont finies.
fin[j.nom] = Math.max(...j.depend.map((d) => fin[d])) + j.duree;
}
reste = reste.filter((j) => !pret.includes(j));
}
const total = Math.max(...Object.values(fin));
const dernier = Object.entries(fin).find(([, v]) => v === total)[0];
return { total, dernier, fin };
}
for (const [nom, inst] of [["sans cache", 95], ["avec cache", 8]]) {
const r = cheminCritique(JOBS, inst);
console.log(" " + nom.padEnd(12) + Math.round(r.total / 60) + " min " +
(r.total % 60) + " s (chemin critique se termine sur « " + r.dernier + " »)");
}
console.log(" Le cache gagne 87 s. Le chemin critique, lui, vaut " +
Math.round(cheminCritique(JOBS, 8).total / 60) + " min :");
console.log(" installation → unitaires → intégration → bout en bout. Paralléliser");
console.log(" lint et sécurité ne change RIEN au total, puisqu'ils ne sont pas");
console.log(" sur ce chemin — c'est le chemin critique du cours d'architecture.");
console.log(" Pour descendre sous dix minutes, il faut découper le bout en bout.");
console.log("");
console.log("— 3. les tests instables —");
const probaEchecPipeline = (n, p) => 1 - Math.pow(1 - p, n);
console.log(" tests | instabilité | pipeline rouge sans cause");
for (const [n, p] of [[50, 0.005], [200, 0.005], [200, 0.001], [500, 0.002], [1000, 0.005]]) {
console.log(" " + String(n).padStart(5) + " | " + (p * 100).toFixed(1).padStart(10) + " %" +
" | " + (probaEchecPipeline(n, p) * 100).toFixed(0).padStart(20) + " %");
}
console.log(" 200 tests à 0,5 % : 63 % des exécutions échouent sans raison.");
console.log("");
console.log("— ce que fait une équipe qui relance « pour voir » —");
let vraisEchecsRates = 0, relances = 0;
const N = 1000, INSTABILITE = 0.005, NB_TESTS = 200, VRAI_DEFAUT = 0.15;
for (let i = 0; i < N; i++) {
const vraiDefaut = Math.random() < VRAI_DEFAUT;
const instable = Math.random() < probaEchecPipeline(NB_TESTS, INSTABILITE);
if (!vraiDefaut && !instable) continue;
// L'équipe a appris que « rouge » signifie souvent « instable » : elle relance.
relances++;
// Une relance sur un vrai défaut échoue encore ; sur une instabilité, elle
// passe souvent — d'où le réflexe. Mais certains vrais défauts sont aussi
// intermittents, et ceux-là passent à la relance : ils sont ignorés.
if (vraiDefaut && Math.random() < 0.3) vraisEchecsRates++;
}
console.log(" sur " + N + " exécutions : " + relances + " relances « pour voir »,");
console.log(" et " + vraisEchecsRates + " vrais défauts passés inaperçus parce qu'ils");
console.log(" étaient eux-mêmes intermittents.");
console.log(" C'est le vrai coût de l'instabilité : elle ne fait pas perdre du");
console.log(" temps, elle DÉTRUIT LE SIGNAL. Un test instable se répare ou se");
console.log(" supprime le jour même.");
En travaux pratiques
Travaux pratiques 5 · 4 h
Construire le pipeline, et le rendre rapide
Écrire un pipeline complet sur le dépôt du fil rouge, puis le mesurer et le raccourcir — ordre des étapes, cache, parallélisme.
Avant de commencer
- Le dépôt du TP 2 hébergé sur une forge disposant d'intégration continue
- La pile Compose du TP 4, qui démarre
- Une suite de tests, même minimale
Énoncé
- Un premier pipeline qui échoue — Écrivez un pipeline déclenché sur chaque demande de fusion : installation, analyse statique, tests. Poussez une modification qui casse volontairement le lint, et vérifiez que la demande de fusion est bloquée.
- Mesurer le délai avant retour — Placez délibérément les étapes de la plus lente à la plus rapide. Poussez une faute de lint et chronométrez le temps entre le push et le message d'échec. Réordonnez, refaites la mesure.
- Mettre les dépendances en cache — Activez le cache du gestionnaire de paquets et comparez la durée d'installation sur deux exécutions consécutives.
- Ajouter les tests d'intégration — Faites tourner les tests contre une vraie base, démarrée par le pipeline. Ils ne doivent PAS dépendre les uns des autres : chaque test crée et détruit ses propres données. Indice : Les forges offrent des services attachés à un job, avec une sonde de santé — c'est le depends_on du TP 4, transposé.
- Paralléliser, et trouver la limite — Découpez en jobs indépendants et mesurez le gain. Identifiez ensuite le chemin critique : quelle suite d'étapes fixe la durée totale ? Que gagneriez-vous en parallélisant ce qui n'est PAS sur ce chemin ?
- Éprouver l'instabilité — Introduisez un test qui échoue aléatoirement une fois sur vingt. Relancez le pipeline dix fois et comptez les échecs. Discutez : que fera l'équipe au bout d'une semaine ?
- Publier l'image — Ajoutez un job final qui construit l'image du TP 3 et la pousse dans un registre, étiquetée avec l'empreinte du commit. C'est cet artefact que le TP 6 déploiera.
C'est réussi quand
- Une faute de lint est signalée en moins de trente secondes
- Le pipeline complet dure moins de dix minutes
- Deux exécutions consécutives sans changement de dépendances ne réinstallent rien
- L'image publiée porte l'empreinte du commit qui l'a produite
Correction
name: intégration
on:
pull_request:
push:
branches: [main]
jobs:
rapide:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- run: npm run lint # 10 s : en premier
- run: npm run test:unit # 60 s
integration:
runs-on: ubuntu-latest
services:
basededonnees:
image: postgres:16-alpine
env: { POSTGRES_USER: app, POSTGRES_PASSWORD: secret }
options: >-
--health-cmd "pg_isready -U app"
--health-interval 5s --health-retries 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- run: npm run test:integration
env:
DATABASE_URL: postgres://app:secret@localhost:5432/app
image:
needs: [rapide, integration]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t registre/tickets:${{ github.sha }} .
- run: docker push registre/tickets:${{ github.sha }}Le job « rapide » et le job « integration » tournent en parallèle ; la publication de l'image attend les deux, parce qu'on ne publie que ce qui est vérifié. L'étiquette est l'empreinte du commit : c'est ce qui donne la traçabilité du conteneur jusqu'à la ligne de code.
ordre lent → rapide : faute de lint signalée après 16 min 10 s ordre rapide → lent : faute de lint signalée après 12 s
La première étape qui échoue arrête le job : l'ordre décide donc du délai avant le retour. Trois lignes déplacées, et la faute remonte tant que le code est encore sous les yeux — au lieu d'arriver quand on est passé à autre chose.
installation 8 s ─┬─ lint 10 s
├─ unitaires 60 s ── intégration 300 s
└─ sécurité 120 s
durée totale = 8 + 60 + 300 = 368 sParalléliser lint et sécurité ne change RIEN au total : ils ne sont pas sur le chemin critique. Pour descendre, il faut s'attaquer aux tests d'intégration — les découper et les répartir entre plusieurs jobs. C'est le chemin critique du cours d'architecture, appliqué à un pipeline.
Un test qui échoue une fois sur vingt fait échouer le pipeline dans 5 % des cas ; avec deux cents tests à 0,5 %, on atteint 63 %. Au bout d'une semaine, l'équipe relance « pour voir » au lieu de lire l'échec — et un vrai défaut passe alors inaperçu. La règle est de réparer ou supprimer le test le jour même : le garder est pire que ne pas l'avoir, parce qu'il détruit le signal.
beforeEach(async () => {
schema = "test_" + Math.random().toString(36).slice(2, 8);
await db.query("CREATE SCHEMA " + schema);
await migrer(schema);
});
afterEach(async () => {
await db.query("DROP SCHEMA " + schema + " CASCADE");
});Chaque test crée son propre espace et le détruit. Sans cela, les tests ne passent que dans un ordre donné et deviennent instables dès qu'on les parallélise — ce qui ramène au point précédent.
Ce que la suite en fait
Le chapitre 6 prolonge le pipeline jusqu'à la production. Tout ce qui vient d'être posé y reste valable : le même déclencheur, les mêmes artefacts — et l'image construite ici devient exactement celle qui sera déployée, ce qui est la garantie centrale de tout le bloc.
C'est aussi là que la discipline de ce chapitre prend son sens complet : on ne déploie automatiquement que ce en quoi on a confiance, et cette confiance vient précisément d'un pipeline vert que personne n'a appris à ignorer.
À retenir
Flashcards · 5 cartes
- Quelles sont les quatre règles de l'intégration continue, et pourquoi l'outil ne suffit pas ?
- 1) INTÉGRER au moins une fois par jour dans la branche principale. 2) Chaque intégration déclenche une VÉRIFICATION AUTOMATIQUE — manuelle, elle est sautée un vendredi soir. 3) Une construction CASSÉE se répare tout de suite : tant que la branche est rouge, plus personne ne peut savoir si son travail est correct. 4) PERSONNE NE CONSTRUIT par-dessus du rouge. Sans ces règles on obtient un pipeline qui échoue depuis trois semaines et que tout le monde ignore.
- Nommez les cinq notions d'un pipeline.
- Le DÉCLENCHEUR (push, demande de fusion, étiquette, horaire). Le JOB, unité exécutée sur une machine propre — deux jobs indépendants tournent en parallèle. L'ÉTAPE, commande dans un job, la première qui échoue arrêtant le job. L'ARTEFACT, fichier produit et conservé (rapport, image, binaire), qui sert aussi à passer un résultat d'un job à l'autre. Le CACHE, qui conserve entre deux exécutions ce qui est coûteux à reconstituer.
- Quels sont les deux leviers pour raccourcir un pipeline, et quelle limite ?
- 1) ORDONNER DU PLUS RAPIDE AU PLUS LENT : lint 10 s, unitaires 1 min, intégration 5 min, bout en bout 15 min — une faute de lint remonte alors en dix secondes au lieu de vingt minutes. 2) PARALLÉLISER ce qui est indépendant. LIMITE : le pipeline dure aussi longtemps que son CHEMIN LE PLUS LONG — paralléliser ce qui est déjà rapide ne gagne rien, il faut s'attaquer aux tests de bout en bout. Seuil pratique : au-delà de dix minutes, on ne l'attend plus.
- Pourquoi la couverture de tests est-elle un bon signal et un mauvais objectif ?
- Elle mesure les lignes EXÉCUTÉES, pas les comportements VÉRIFIÉS : un test sans assertion donne 100 % et ne détecte rien. Dès qu'un seuil devient contraignant, c'est ce chemin-là que la pression fait émerger — et 90 % de couverture creuse est plus dangereux que 40 % assumés, car le tableau de bord affiche du vert. Règle générale : quand une mesure devient un objectif, elle cesse d'être une bonne mesure. On surveille la TENDANCE, et surtout ce qui n'est couvert par AUCUN test.
- Pourquoi un test instable est-il pire que pas de test, et quel est le calcul ?
- Si chaque test échoue au hasard avec une probabilité p, la probabilité qu'au moins un des n échoue vaut 1 − (1−p)^n : avec p = 0,5 % et 200 tests, 63 % d'échecs SANS AUCUNE CAUSE RÉELLE. L'équipe relance « pour voir » et APPREND À IGNORER les échecs — un vrai échec passe alors inaperçu. Un test instable se répare ou se supprime le jour même. Autres maux : les pipelines lents, qui poussent à regrouper les commits, et les tests DÉPENDANTS entre eux, qui deviennent instables dès qu'on parallélise.