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 :
| Niveau | Ce qu'il vérifie | Question |
|---|---|---|
| Unitaire | une fonction, une classe, isolée | ce morceau est-il correct ? |
| Intégration | plusieurs modules ensemble | s'assemblent-ils bien ? |
| Recette | le logiciel entier, du point de vue du client | fait-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.
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 ?
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.
Une équipe atteint 100 % de couverture de code par ses tests. Peut-elle en conclure que son logiciel est sans bug ?
À 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.
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.
// 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."));
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
Vous avez parcouru les 7 sections.
Marquez-la terminée pour faire avancer votre parcours, ou revenez sur un point avant de passer à la suite.