C5 — Tests et outillageDans le dialogue d’impression, choisissez « Enregistrer au format PDF » comme destination.
Retour

Licence 1 · Génie logiciel

Cours 5Tests et outillage

Éprouver son code plutôt que de l'espérer correct, et travailler à plusieurs sans se marcher dessus grâce à Git.

2 chapitres · 9 h de travail estimé

  1. 1. Tests5 h
  2. 2. Gestion de versions et collaboration4 h

Chapitre 1 · 5 h

Tests

Pourquoi « ça marche chez moi » ne suffit pas ; tests unitaires, d'intégration, de recette ; jeux de tests et cas limites ; framework de test ; couverture ; tests de non-régression.

« Ça marche chez moi. » Cette phrase, tout développeur l'a prononcée — juste avant que ça ne marche plus chez le client. Le bloc V attaque les deux outils qui transforment les principes des chapitres précédents en pratique fiable : les tests et Git. Ce chapitre traite les tests — et il faut être honnête sur un point : leur valeur ne se croit pas, elle s'éprouve. Tant qu'on n'a pas vu une modification innocente casser silencieusement un programme, on tient les tests pour une corvée. L'exercice est là pour provoquer cette prise de conscience.

Pourquoi tester, et pourquoi « ça marche chez moi » ne suffit pas

Tester, c'est exécuter le logiciel pour vérifier qu'il fait ce qu'on attend — systématiquement, et pas seulement sur les quelques cas qu'on a en tête. Le piège de « ça marche chez moi » est double :

  • on teste le cas nominal (celui auquel on pensait en codant) et on oublie les cas limites, là où les bugs se cachent ;
  • on teste une fois, à la main, puis on modifie le code — sans revérifier que l'ancien comportement tient toujours.

Un exemple, celui de l'exercice : une fonction « année bissextile » testée sur 2020 (vrai) et 2021 (faux) semble correcte. Mais elle peut se tromper complètement sur 1900 et 2000 — les siècles, où la règle est subtile. Deux essais bien choisis ne couvrent pas un comportement. Il faut des tests pensés pour débusquer les cas où la logique est fragile.

Le retour au chapitre 2 est direct : un bug attrapé par un test coûte 1 ; le même bug découvert par l'utilisateur en production coûte 50 à 100. Les tests sont l'outil qui rapproche la détection du moment de l'erreur.

Les niveaux de tests

On teste à plusieurs échelles, chacune répondant à une question différente :

NiveauCe qu'il vérifieQuestion
Unitaireune fonction, une classe, isoléece morceau est-il correct ?
Intégrationplusieurs modules ensembles'assemblent-ils bien ?
Recettele logiciel entier, du point de vue du clientfait-il ce qui était demandé ?

Ces niveaux se complètent, ils ne se remplacent pas. Deux fonctions parfaitement correctes séparément (tests unitaires au vert) peuvent mal fonctionner ensemble (test d'intégration au rouge) : mauvais format d'échange, hypothèses incompatibles — c'est exactement le scénario du Mars Climate Orbiter (chapitre 1). Et un logiciel dont tous les modules s'intègrent bien peut malgré tout ne pas répondre au besoin : les tests de recette ferment la boucle avec le cahier des charges (chapitre 4) — ils vérifient chaque besoin bien formulé, transformé en test qui répond par oui ou par non.

Quiz · 1 question

Deux fonctions passent tous leurs tests unitaires, mais le logiciel échoue quand elles sont utilisées ensemble. Quel niveau de test aurait dû l'attraper, et qu'est-ce que cela illustre ?

  • Rien : si les tests unitaires passent, le logiciel est forcément correctunitaire suffit
  • Les tests d'intégration : des modules corrects séparément peuvent mal fonctionner ensemble (format d'échange, hypothèses incompatibles) — les niveaux de tests se complètentintégration : l'assemblage
  • Les tests de recette, qui remplacent les tests unitairesrecette remplace unitaire

Réponse : Les tests unitaires vérifient chaque morceau ISOLÉMENT ; ils ne disent rien de leur ASSEMBLAGE. Deux fonctions correctes séparément peuvent échouer ensemble — unités incompatibles (Mars Climate Orbiter), format d'échange mal accordé, hypothèses divergentes. C'est le rôle des tests d'INTÉGRATION. Les niveaux se complètent et ne se remplacent pas : unitaire (le morceau), intégration (l'assemblage), recette (le besoin du client). Passer l'un ne dispense pas des autres.

Jeux de tests et cas limites

Un jeu de tests est l'ensemble des cas qu'on éprouve. Sa qualité ne tient pas au nombre, mais au choix des cas. Un bon jeu couvre trois familles :

  • le cas nominal : l'usage normal, attendu (une année ordinaire) ;
  • les cas limites : les frontières, où la logique bascule — zéro, valeur maximale, liste vide, premier et dernier élément, les fameux siècles de l'exemple bissextile ;
  • les cas d'erreur : les entrées invalides — que fait le code face à une valeur négative, à un format inattendu ?

La règle empirique, vérifiée par l'expérience : les bugs se cachent aux frontières, pas au milieu. Un développeur qui n'écrit que des tests nominaux passe précisément à côté des cas où le code se trompe. Chercher activement « qu'est-ce qui pourrait mal tourner ? » est le bon état d'esprit — l'inverse de celui qui écrit le code.

Frameworks, couverture, non-régression

En pratique, on n'écrit pas le mécanisme de test à la main : un framework le fournit — JUnit en Java, pytest en Python. Il offre des assertions (assertEqual(obtenu, attendu)), exécute tous les tests d'une commande, et produit un rapport clair : combien passent, lesquels échouent, et pourquoi. Écrire un test devient aussi rapide qu'écrire la fonction.

La couverture mesure quelle part du code les tests exécutent (quelles lignes, quelles branches). Utile pour repérer le code jamais testé — mais un piège si on en fait un but : 100 % de couverture ne prouve pas l'absence de bug. Dans l'exemple bissextile, tester 2020 et 2021 exécute toutes les lignes de la version buguée sans jamais révéler l'erreur des siècles. La couverture dit ce qui est exercé, pas ce qui est correct.

Enfin, le test de non-régression est le plus précieux pour un logiciel qui dure : rejouer automatiquement tous les tests après chaque modification, pour garantir qu'on n'a pas re-cassé ce qui marchait. C'est lui qui rend le changement sûr — et donc qui rend possibles le refactoring (chapitre 7) et la maintenance (chapitre 1). Sans tests de non-régression, tout logiciel qui vit finit par se figer, chacun ayant peur d'y toucher.

Quiz · 1 question

Une équipe atteint 100 % de couverture de code par ses tests. Peut-elle en conclure que son logiciel est sans bug ?

  • Oui : 100 % de couverture signifie que tout le code est testé et correctcouverture = correction
  • Non : la couverture dit quelles lignes sont EXÉCUTÉES par les tests, pas si les cas testés sont les bons — on peut exécuter tout le code sans tester les cas limites où le bug se cacheexécuté ≠ correct
  • Non, car la couverture à 100 % est techniquement impossibleimpossible

Réponse : La couverture mesure ce qui est EXÉCUTÉ par les tests, pas ce qui est CORRECT. On peut atteindre 100 % en exécutant toutes les lignes sans jamais éprouver les cas limites : tester la fonction bissextile sur 2020 et 2021 couvre toutes ses lignes tout en manquant l'erreur des siècles. La couverture est utile pour repérer le code jamais testé, mais en faire un but donne une fausse assurance. La qualité d'un jeu de tests tient au CHOIX des cas (nominal, limites, erreur), pas au pourcentage de lignes touchées.

À vous

Voici l'exercice qui rend les tests crédibles — celui que le cours recommande de faire vivre plutôt que de raconter. Un collègue « simplifie » une fonction bissextile et introduit, sans le voir, une régression sur les siècles. Sans tests, ce bug part en production.

À vous d'écrire les tests — cas limites compris — qui l'attrapent avant la livraison. Vous constaterez de vos yeux la différence entre « ça marche chez moi » (deux cas nominaux) et un vrai jeu de tests qui débusque l'erreur là où elle se cache.

Exercice de code

Un collègue « simplifie » une fonction bissextile et introduit une régression sur les siècles. Écrivez les tests — cas limites compris — qui l'attrapent avant la production. Constatez de vos yeux pourquoi « ça marche chez moi » ne suffit pas, et ce qu'est un test de non-régression.

Point de départ

// La fonction ORIGINALE, correcte : une année est bissextile si elle est
// divisible par 4, SAUF les siècles non divisibles par 400 (1900 non, 2000 oui).
function estBissextileOriginale(a) {
  if (a % 400 === 0) return true;
  if (a % 100 === 0) return false;
  return a % 4 === 0;
}

// Un collègue "simplifie" la fonction, persuadé que « ça marche » — il a
// testé sur 2020 (vrai) et 2021 (faux), ça passe, il livre.
function estBissextileModifiee(a) {
  return a % 4 === 0;   // RÉGRESSION : oublie la règle des siècles !
}

// ── À VOUS : écrire des tests qui ATTRAPENT la régression ───────────────────
// Un bon jeu de tests couvre le cas nominal ET les CAS LIMITES. Ici, les cas
// limites sont les siècles : 1900 (non bissextile), 2000 (bissextile).
// Complétez CAS avec des couples [annee, resultatAttendu] qui révèlent le bug.
const CAS = [
  [2020, true],
  [2021, false],
  // à compléter : ajoutez les cas limites qui distinguent les deux versions
];

// Le "harnais" de test : compare chaque version à l'attendu.
function tester(fn, nom) {
  let echecs = 0;
  for (const [a, attendu] of CAS) {
    const obtenu = fn(a);
    if (obtenu !== attendu) { echecs++; console.log("   ✗ " + nom + "(" + a + ") = " + obtenu + ", attendu " + attendu); }
  }
  console.log(nom + " : " + (CAS.length - echecs) + "/" + CAS.length + " tests passés");
  return echecs === 0;
}

console.log("Tests sur la version originale :");
tester(estBissextileOriginale, "originale");
console.log("\nMêmes tests sur la version modifiée :");
const okModif = tester(estBissextileModifiee, "modifiée");
console.log("\n" + (okModif
  ? "⚠ Vos tests ne détectent PAS la régression : ajoutez les cas limites (siècles)."
  : "✓ Vos tests ATTRAPENT la régression — elle n'atteindra pas la production."));

Solution

const CAS = [
  [2020, true],    // divisible par 4 : bissextile (cas nominal)
  [2021, false],   // non divisible par 4 : non (cas nominal)
  [1900, false],   // SIÈCLE non divisible par 400 : NON bissextile  <- cas limite
  [2000, true],    // siècle divisible par 400 : bissextile          <- cas limite
  [2100, false],   // autre siècle non /400 : non                    <- cas limite
];
// Sur la version originale : 5/5 passés.
// Sur la version modifiée : 1900 et 2100 échouent (elle les croit bissextiles).
// -> les tests ATTRAPENT la régression.

// ── Ce que l'exercice enseigne ──────────────────────────────────────────────
//
// 1. « ÇA MARCHE CHEZ MOI » NE SUFFIT PAS. Le collègue a testé 2020 et 2021,
//    a vu « ça marche », et a livré un bug. Deux essais bien choisis ne
//    couvrent pas le comportement — il faut les CAS LIMITES, là où la logique
//    est subtile (ici, la règle des siècles).
//
// 2. LES TESTS SONT UN FILET. Sans eux, la régression part en production et
//    c'est l'utilisateur qui la découvre (au coût 50-100×, chapitre 2). Avec
//    eux, elle est attrapée AVANT la livraison, en une seconde. C'est
//    exactement pourquoi on ne refactore jamais sans tests (chapitre 7).
//
// 3. UN BON JEU DE TESTS couvre : le cas nominal, les CAS LIMITES (frontières,
//    zéro, valeurs spéciales) et les cas d'ERREUR. Les bugs se cachent aux
//    frontières, pas au milieu.
//
// 4. TEST DE NON-RÉGRESSION : rejouer automatiquement TOUS les anciens tests
//    après chaque modification, pour garantir qu'on n'a pas re-cassé ce qui
//    marchait. C'est ce qui rend le changement SÛR sur la durée — le socle de
//    la maintenance (chapitre 1).
//
// 5. En vrai, on n'écrit pas le harnais à la main : un FRAMEWORK (JUnit en
//    Java, pytest en Python) le fournit — assertions, exécution, rapport — et
//    mesure la COUVERTURE (quelle part du code les tests exercent). Mais 100 %
//    de couverture ne prouve pas l'absence de bug : ici, couvrir 2020/2021
//    exécute toutes les lignes de la version modifiée sans voir l'erreur.

Ce que la suite en fait

Les tests protègent votre code des régressions. Reste l'autre grand risque d'un projet d'équipe : se marcher dessus à plusieurs sur le même code. C'est ce que règle la gestion de versions.

Le chapitre 9 traite Git — comment plusieurs personnes modifient le même projet sans se détruire le travail, comment revenir en arrière, et comment résoudre calmement le conflit qui survient inévitablement quand deux coéquipiers touchent la même ligne. Comme pour les tests, sa valeur s'éprouve : vous provoquerez un conflit pour apprendre à le résoudre à froid, avant de le rencontrer en pleine échéance de projet.

À retenir

Flashcards · 4 cartes

Pourquoi « ça marche chez moi » ne suffit-il pas comme test ?
Parce qu'on teste alors seulement le CAS NOMINAL (celui auquel on pensait) et une seule fois, à la main. On oublie les CAS LIMITES où les bugs se cachent (les siècles pour une fonction bissextile), et on ne revérifie pas après modification. Deux essais bien choisis ne couvrent pas un comportement. Un bug non testé part en production, au coût 50-100× (chapitre 2) : les tests rapprochent la détection du moment de l'erreur.
Quels sont les trois niveaux de tests, et pourquoi se complètent-ils ?
UNITAIRE (une fonction/classe isolée : est-elle correcte ?), INTÉGRATION (plusieurs modules ensemble : s'assemblent-ils bien ?), RECETTE (le logiciel entier vu du client : fait-il ce qui était demandé ?). Ils se complètent : des modules corrects séparément peuvent échouer ensemble (intégration), et un logiciel bien intégré peut ne pas répondre au besoin (recette, qui ferme la boucle avec le cahier des charges).
Que doit couvrir un bon jeu de tests, et où se cachent les bugs ?
Trois familles : le cas NOMINAL (usage normal), les CAS LIMITES (frontières : zéro, max, liste vide, premier/dernier, valeurs spéciales) et les cas d'ERREUR (entrées invalides). Règle empirique : les bugs se cachent aux FRONTIÈRES, pas au milieu. La qualité d'un jeu de tests tient au choix des cas, pas à leur nombre — chercher « qu'est-ce qui pourrait mal tourner ? ».
Que mesure la couverture, et qu'est-ce qu'un test de non-régression ?
La COUVERTURE mesure quelle part du code les tests EXÉCUTENT — utile pour repérer le code jamais testé, mais 100 % ne prouve pas l'absence de bug (exécuté ≠ correct : on peut couvrir toutes les lignes sans tester les cas limites). Le test de NON-RÉGRESSION rejoue automatiquement tous les tests après chaque modification, pour garantir qu'on n'a pas re-cassé ce qui marchait : c'est lui qui rend le changement sûr, donc le refactoring et la maintenance possibles.

Chapitre 2 · 4 h

Gestion de versions et collaboration

Git : dépôt, commit, historique ; branches et fusion ; résolution de conflits ; dépôt distant et travail à plusieurs ; suivi des tâches ; bonnes pratiques de messages de commit.

Un projet d'équipe pose un problème que le code seul ne résout pas : plusieurs personnes modifient les mêmes fichiers en même temps. Sans outil, c'est le chaos — on s'envoie des fichiers par courriel, on écrase le travail de l'autre, on ne sait plus quelle version est la bonne. La gestion de versions règle ce problème, et Git en est l'outil universel. Comme pour les tests (chapitre 8), sa valeur s'éprouve plutôt qu'elle ne se raconte : tant qu'on n'a pas perdu du travail faute de versionnage, ou résolu un conflit dans la panique, on croit pouvoir s'en passer.

Dans votre projet, Git s'utilise dès la première semaine — pas à la fin. C'est l'infrastructure de tout le reste.

Le dépôt, le commit, l'historique

Un dépôt (repository) est un dossier de projet dont Git suit l'histoire complète. À chaque étape stable de votre travail, vous créez un commit : un instantané de l'état du projet, daté, signé, accompagné d'un message qui décrit ce que vous avez changé.

L'ensemble des commits forme l'historique — une suite d'instantanés qui raconte la vie du projet. Cet historique n'est pas décoratif : il permet de revenir en arrière (annuler une modification qui casse tout), de comprendre pourquoi une ligne a été écrite (qui, quand, dans quel commit), et de retrouver l'état exact du projet à n'importe quelle date. Un projet versionné n'a jamais « perdu » de travail : tout état passé est récupérable.

Le message de commit mérite un soin particulier, car il s'adresse au futur — vos coéquipiers, et vous-même dans six mois. Un bon message dit ce qui a changé et pourquoi, en une ligne claire : Corrige le calcul de TVA sur les livres vaut infiniment mieux que modif, fix, ou ça marche enfin. L'historique est une documentation gratuite — à condition d'écrire les messages pour qu'on puisse les relire.

Quiz · 1 question

Qu'est-ce qu'un bon message de commit, et pourquoi cela compte-t-il ?

  • Un message court comme « modif » ou « fix » : l'important est de committer souventcourt peu importe le sens
  • Un message qui dit ce qui a changé ET pourquoi, en une ligne claire, car l'historique s'adresse au futur (coéquipiers, soi-même plus tard) et sert de documentationquoi + pourquoi, pour le futur
  • Un message très long qui recopie tout le code modifiérecopier le code

Réponse : L'historique Git est une documentation gratuite du projet — à condition d'être lisible. « modif » ou « fix » ne disent rien : dans six mois, personne ne saura ce que ce commit a changé ni pourquoi. Un bon message est une ligne claire indiquant le QUOI et le POURQUOI (« Corrige le calcul de TVA sur les livres »). Inutile d'y recopier le code (Git le montre déjà via le diff) : le message explique l'intention, que le code ne dit pas — même esprit que le commentaire utile du chapitre 6.

Branches et fusion

Une branche est une ligne de développement parallèle. Plutôt que de travailler tous sur la même version — au risque de se casser mutuellement le code —, chacun crée une branche pour sa tâche : développer une fonctionnalité, corriger un bug, sans perturber le travail des autres ni la version stable.

Quand la tâche est finie et testée, on fusionne (merge) la branche dans la branche principale : Git combine les modifications. La plupart du temps, la fusion est automatique — Git sait réconcilier des changements qui portent sur des lignes ou des fichiers différents. C'est ce qui permet à trois personnes de travailler en parallèle et de réunir leur travail sans effort.

Ce modèle — une branche par tâche, fusion une fois testée — est la manière standard de travailler à plusieurs. Il isole le travail en cours (une fonctionnalité à moitié faite ne casse pas la branche principale) et rend chaque contribution relisable avant d'être intégrée (la revue de code du chapitre 6).

Le conflit, et comment le résoudre

Reste le cas que Git ne peut pas résoudre seul : deux personnes ont modifié la même ligne à partir de la même version. Git ne peut pas deviner laquelle garder — c'est un conflit.

Il faut d'emblée corriger un contresens : un conflit n'est pas une erreur, ni une faute. C'est une situation normale du travail à plusieurs. Git ne bloque que là où il serait dangereux de choisir à votre place ; pour tout le reste, il fusionne seul. Face à un conflit, il affiche les deux versions entre des marqueurs et attend :

<<<<<<< votre version  return montant * 1.20;=======  return montant * 0.90;>>>>>>> version du coéquipier

Résoudre, c'est décider — en humain — de la version finale, puis supprimer les marqueurs. Et décider exige de comprendre les deux intentions : ici, l'un a ajouté la TVA, l'autre une remise ; la bonne résolution n'est ni l'une ni l'autre mais les deux combinées. C'est pourquoi aucun outil ne peut résoudre à votre place : il faudrait comprendre le sens du code.

Deux pièges du débutant, à éviter : paniquer et « prendre le sien » en écrasant le travail de l'autre ; et oublier un marqueur, qui laisse du code qui ne compile pas. La bonne attitude est calme et méthodique : lire les deux versions, comprendre, combiner, nettoyer les marqueurs, tester. L'exercice vous fait vivre exactement cette séquence.

Provoquer un conflit pour l'apprivoiser

C'est le point que le cours insiste à faire vivre en TP. Rencontré pour la première fois en pleine échéance de projet, un conflit affole : on résout n'importe comment, on perd du travail, on accuse l'outil. Provoqué délibérément — deux étudiants modifient volontairement la même ligne, puis fusionnent —, il devient une manipulation banale, apprise à froid.

C'est le même principe que l'exercice de régression du chapitre 8 : la valeur d'une pratique ne se croit pas, elle s'éprouve. Faites-le une fois en TP, calmement, et le conflit cesse d'être un épouvantail. On réduit d'ailleurs leur fréquence par de bonnes habitudes — commits petits et fréquents, communication (« je touche à prix.js cet après-midi »), découpage du travail en zones distinctes — mais on ne les élimine jamais totalement à plusieurs. Savoir les résoudre est une compétence, pas un échec.

Quiz · 1 question

Deux coéquipiers ont modifié la même ligne ; Git signale un conflit. Quelle est la bonne façon de le résoudre ?

  • C'est une erreur grave : il faut annuler l'un des deux commits et recommencererreur à annuler
  • Comprendre les deux intentions, écrire la version finale qui les concilie (ici garder les deux changements), puis supprimer les marqueurs de conflit et testercomprendre et concilier
  • Toujours garder sa propre version, car c'est la plus récente qu'on connaîtgarder la sienne

Réponse : Un conflit n'est pas une erreur mais une situation normale : Git ne peut pas deviner l'intention quand la même ligne a changé des deux côtés. La bonne résolution demande de COMPRENDRE les deux modifications et d'écrire la version finale voulue — souvent en les combinant (garder la TVA ET la remise), pas en sacrifiant l'une. Il faut ensuite retirer les marqueurs (<<<<, ====, >>>>) et tester. « Toujours garder la sienne » écrase le travail du coéquipier ; annuler un commit détruit du travail valable.

Dépôt distant et collaboration

Jusqu'ici, le dépôt vit sur une machine. Le dépôt distant (sur GitHub, GitLab…) en est une copie partagée, hébergée, qui sert de point de rendez-vous de l'équipe. Chacun envoie (push) ses commits vers le distant et récupère (pull) ceux des autres. Le distant est aussi une sauvegarde : si une machine tombe, le projet survit.

Autour du dépôt, ces plateformes offrent des outils de collaboration essentiels au projet :

  • le suivi des tâches et des bugs (issues) : une liste partagée de ce qu'il reste à faire et des problèmes connus — le backlog du chapitre 3, rendu concret ;
  • les demandes de fusion (pull/merge requests) : proposer une branche à intégrer, la faire relire par un coéquipier (revue de code, chapitre 6) avant de fusionner ;
  • l'historique partagé : qui a fait quoi, quand — précieux pour se coordonner.

Pour votre projet, l'essentiel tient en une routine : committer souvent avec des messages clairs, pousser régulièrement vers le distant, récupérer avant de commencer à travailler. Cette discipline, prise dès la première semaine, évite l'immense majorité des drames de coordination.

À vous

L'exercice fait vivre le moment redouté, mais à froid : deux coéquipiers modifient la même ligne, Git lève un conflit, et vous le résolvez. Vous lisez les marqueurs, vous comprenez les deux intentions — une TVA, une remise —, vous écrivez la version qui les concilie, et vous nettoyez les marqueurs.

C'est exactement la manipulation qui, apprise ici calmement, vous évitera la panique le jour où elle surviendra en pleine échéance de projet. Un conflit résolu proprement est un non-événement.

Exercice de code

Deux coéquipiers modifient la même ligne : provoquez le conflit Git, lisez les marqueurs (<<<<, ====, >>>>), puis résolvez-le en combinant les deux intentions et en supprimant les marqueurs. Apprenez à le faire à froid, avant de le rencontrer en pleine échéance.

Point de départ

// Deux membres de l'équipe partent de la MÊME version (la « base ») et
// modifient CHACUN la même ligne, sur leur branche. À la fusion, Git ne peut
// pas deviner laquelle garder : c'est un CONFLIT.

const base = [
  "function prix(montant) {",
  "  return montant;",              // <- la ligne que les DEUX vont changer
  "}",
];

// Amina ajoute la TVA :
const amina = [
  "function prix(montant) {",
  "  return montant * 1.20;",       // sa version de la ligne 2
  "}",
];

// Bakary ajoute une remise :
const bakary = [
  "function prix(montant) {",
  "  return montant * 0.90;",       // sa version de la ligne 2
  "}",
];

// Ce que Git AFFICHE quand il ne peut pas trancher (marqueurs de conflit) :
function fusionner(base, a, b) {
  const res = [];
  for (let i = 0; i < base.length; i++) {
    if (a[i] === b[i]) { res.push(a[i]); }          // personne n'a changé, ou même changement
    else if (a[i] === base[i]) { res.push(b[i]); }  // seul b a changé -> on prend b
    else if (b[i] === base[i]) { res.push(a[i]); }  // seul a a changé -> on prend a
    else {                                          // les DEUX ont changé -> CONFLIT
      res.push("<<<<<<< amina", a[i], "=======", b[i], ">>>>>>> bakary");
    }
  }
  return res;
}

console.log("Ce que Git affiche à la fusion :");
console.log(fusionner(base, amina, bakary).join("\n"));

// ── À VOUS : résoudre le conflit ────────────────────────────────────────────
// Git ne choisit pas : c'est à un HUMAIN de décider, en COMPRENANT les deux
// intentions. Ici, il faut la TVA (Amina) ET la remise (Bakary) : les deux.
// Écrivez la version résolue, SANS marqueur de conflit.
const resolu = [
  "function prix(montant) {",
  // à compléter : la ligne qui combine les deux intentions
  "}",
];

console.log("\nVotre résolution :");
console.log(resolu.join("\n"));
console.log("\nContient encore des marqueurs de conflit ?",
  resolu.some((l) => /^(<<<<|====|>>>>)/.test(l)));

Solution

const resolu = [
  "function prix(montant) {",
  "  return montant * 1.20 * 0.90;",   // TVA (Amina) ET remise (Bakary)
  "}",
];
// Aucun marqueur ne subsiste : le conflit est résolu.

// ── Ce que l'exercice enseigne ──────────────────────────────────────────────
//
// 1. UN CONFLIT N'EST PAS UNE ERREUR, c'est une SITUATION NORMALE : deux
//    personnes ont modifié la même ligne à partir de la même base. Git sait
//    fusionner automatiquement les changements sur des lignes DIFFÉRENTES ;
//    il ne bloque QUE lorsqu'il ne peut pas deviner l'intention — sur la MÊME
//    ligne.
//
// 2. GIT NE CHOISIT PAS À VOTRE PLACE. Il affiche les deux versions entre des
//    marqueurs (<<<<<<< / ======= / >>>>>>>) et attend qu'un HUMAIN décide,
//    parce que résoudre exige de COMPRENDRE les deux intentions — ce qu'aucun
//    outil ne peut faire. Ici : garder la TVA ET la remise, pas l'une OU
//    l'autre.
//
// 3. RÉSOUDRE = éditer le fichier pour obtenir la version voulue, PUIS
//    SUPPRIMER les trois marqueurs. Un marqueur oublié, c'est du code qui ne
//    compile pas — l'erreur classique du débutant paniqué.
//
// 4. Se former À FROID change tout. Rencontré pour la première fois en pleine
//    échéance de projet, un conflit affole et on résout n'importe comment (on
//    « prend le sien », on écrase le travail de l'autre). Provoqué
//    délibérément en TP, il devient une manipulation banale.
//
// 5. On RÉDUIT la fréquence des conflits par de bonnes pratiques : commits
//    petits et fréquents, communication (« je touche à prix.js »), et
//    découpage du travail en fichiers/zones distincts. Mais on ne les
//    supprime jamais totalement à plusieurs : savoir les résoudre calmement
//    est une compétence, pas un échec.

Ce que la suite en fait

Vous disposez maintenant de tout l'outillage : spécifier, concevoir, écrire proprement, tester, versionner. Le dernier bloc assemble le tout — car un projet, ce n'est pas la somme de ces techniques, c'est leur conduite dans le temps, à plusieurs.

Le chapitre 10 traite la conduite de projet : constituer l'équipe, découper en tâches, suivre l'avancement, documenter, et présenter le résultat en soutenance. C'est le bloc qui donne son sens à tous les autres — et qui décrit, en réalité, ce que votre projet de semestre a fait vivre depuis la première semaine.

À retenir

Flashcards · 4 cartes

Qu'est-ce qu'un dépôt, un commit et un historique Git, et à quoi servent-ils ?
Le DÉPÔT est un projet dont Git suit toute l'histoire. Un COMMIT est un instantané daté de l'état du projet, avec un message décrivant le changement. L'HISTORIQUE est la suite des commits : il permet de revenir en arrière, de comprendre pourquoi une ligne existe, et de retrouver n'importe quel état passé. Un projet versionné ne perd jamais de travail. Un bon message de commit dit le QUOI et le POURQUOI, pas « modif ».
Qu'est-ce qu'une branche et une fusion, et pourquoi ce modèle facilite le travail à plusieurs ?
Une BRANCHE est une ligne de développement parallèle : chacun crée la sienne pour sa tâche, sans perturber les autres ni la version stable. Une fois la tâche finie et testée, on FUSIONNE (merge) dans la branche principale. La plupart des fusions sont automatiques (changements sur des lignes/fichiers différents). Ce modèle isole le travail en cours et rend chaque contribution relisable avant intégration.
Qu'est-ce qu'un conflit Git, et comment le résout-on correctement ?
Un conflit survient quand deux personnes modifient la MÊME ligne depuis la même base : Git ne peut pas deviner laquelle garder. Ce n'est PAS une erreur, mais une situation normale. Il affiche les deux versions entre marqueurs (<<<<, ====, >>>>). Résoudre : comprendre les deux intentions, écrire la version finale qui les concilie (souvent les deux combinées), supprimer les marqueurs, tester. Ne jamais « prendre la sienne » en écrasant l'autre.
À quoi sert un dépôt distant, et quelle routine adopter en projet d'équipe ?
Le dépôt DISTANT (GitHub, GitLab) est la copie partagée qui sert de point de rendez-vous et de sauvegarde : chacun pousse (push) ses commits et récupère (pull) ceux des autres. Il offre le suivi des tâches/bugs (issues = le backlog), les demandes de fusion avec revue, et l'historique partagé. Routine : committer souvent avec des messages clairs, pousser régulièrement, récupérer avant de travailler — dès la première semaine.