{
  "chapter": {
    "id": "tests-debogage",
    "level": "premiere",
    "theme": "Programmation",
    "title": "Tests et débogage",
    "description": "Tester un programme : assertions, jeux d'essais,\ntests unitaires, cas limites. Déboguer : lecture de\nla trace d'erreurs, instructions `print` de\ndébogage, débogueur interactif, isolement de bugs,\nréduction au cas minimal.",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition-test"
      ],
      "title": "Notion de test",
      "statement": "Qu'appelle-t-on un **test** dans le\ncontexte du logiciel ?",
      "options": [
        {
          "text": "Une vérification automatique du comportement attendu",
          "correct": true,
          "feedback": "Un test fournit une entrée connue\net compare la sortie obtenue à la\nsortie attendue. Cela permet de\ndétecter aussi bien les\nrégressions que les bugs\nnouveaux.\n"
        },
        {
          "text": "Un type particulier de variable",
          "correct": false,
          "feedback": "Un test n'est pas une variable.\nC'est une procédure de\nvérification.\n"
        },
        {
          "text": "Une question à choix multiple posée à l'utilisateur final",
          "correct": false,
          "feedback": "Cette interprétation littérale est\nsans rapport avec la notion\ninformatique de test logiciel,\nqui vise à vérifier le bon\nfonctionnement d'un programme.\n"
        },
        {
          "text": "Un avertissement émis par le langage Python",
          "correct": false,
          "feedback": "Un avertissement est un message\nenvoyé pendant l'exécution, sans\ncomparaison à une valeur attendue.\nCe n'est pas un test.\n"
        }
      ],
      "explanation": "Une bonne pratique consiste à\ntester systématiquement ses\nfonctions, en particulier dans les\nparties critiques du programme. Un\nprojet sans tests est très\nfragile."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "assert"
      ],
      "title": "Instruction assert",
      "statement": "Que fait l'instruction `assert\ncondition` en Python ?",
      "options": [
        {
          "text": "Si la condition est fausse, elle lève une `AssertionError`",
          "correct": true,
          "feedback": "C'est très utile pour vérifier\ndes invariants. Lorsqu'une\nassertion échoue, le programme\ns'arrête, ce qui permet de\ndétecter les bugs au plus tôt.\n"
        },
        {
          "text": "Elle affiche la condition à l'écran",
          "correct": false,
          "feedback": "L'instruction `assert` n'affiche\nrien lorsque la condition est\nvraie. Elle ne lève une erreur\nque dans le cas contraire.\n"
        },
        {
          "text": "Elle convertit la condition en une valeur booléenne",
          "correct": false,
          "feedback": "La condition est déjà évaluée\ncomme un booléen avant l'appel à\n`assert`. La conversion en tant\nque telle se fait avec\n`bool(...)`.\n"
        },
        {
          "text": "Elle ne fonctionne qu'en mode débogage",
          "correct": false,
          "feedback": "Cette description est inexacte.\nLes assertions sont actives par\ndéfaut, et désactivées seulement\nsi le programme est lancé avec\nl'option `python -O`.\n"
        }
      ],
      "explanation": "Un usage typique :\n`assert n > 0, \"n doit être\npositif\"`. Le message après la\nvirgule apparaît dans l'erreur\naffichée, ce qui aide au\ndébogage."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "tests-unitaires"
      ],
      "title": "Test unitaire",
      "statement": "Qu'est-ce qu'un **test unitaire** ?",
      "options": [
        {
          "text": "Un test sans coût",
          "correct": false,
          "feedback": "Cette interprétation littérale\ndu mot « unitaire » est\nfantaisiste.\n"
        },
        {
          "text": "Un test qui couvre l'ensemble complet du programme",
          "correct": false,
          "feedback": "Un test qui couvre l'ensemble du\nprogramme s'appelle un test\nd'intégration ou un test\nsystème, et non un test\nunitaire.\n"
        },
        {
          "text": "Un test qui dure très longtemps",
          "correct": false,
          "feedback": "C'est plutôt l'inverse. Les tests\nunitaires sont conçus pour être\nrapides, ciblés et automatisés.\n"
        },
        {
          "text": "Un test qui vérifie une seule fonction isolément",
          "correct": true,
          "feedback": "Les tests unitaires sont\nrapides, ciblés et automatisés.\nIls permettent d'isoler les\nbugs : on identifie précisément\nquelle fonction se comporte\nmal.\n"
        }
      ],
      "explanation": "Une organisation classique des\ntests forme une pyramide :\nbeaucoup de tests unitaires (très\nrapides), moins de tests\nd'intégration (plus lents mais\nutiles) et très peu de tests de\nbout en bout (lents et fragiles)."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "exemple-test"
      ],
      "title": "Exemple de test",
      "statement": "Quelle ligne **teste** que `carre(3)\n== 9` ?",
      "options": [
        {
          "text": "`if carre(3) != 9: print(\"erreur\")`",
          "correct": false,
          "feedback": "Cette ligne effectue bien une\nvérification, mais elle\nn'interrompt pas le programme\nen cas d'échec. L'instruction\n`assert` est plus idiomatique\ndans un contexte de test.\n"
        },
        {
          "text": "`assert carre(3) == 9`",
          "correct": true,
          "feedback": "Si `carre(3)` ne vaut pas $9$,\nl'assertion échoue et le\nprogramme s'arrête. Sinon, le\nprogramme continue\nsilencieusement.\n"
        },
        {
          "text": "`carre(3)`",
          "correct": false,
          "feedback": "Cette ligne exécute simplement\nla fonction sans rien vérifier.\nAucune comparaison n'est\neffectuée.\n"
        },
        {
          "text": "`test carre(3) == 9`",
          "correct": false,
          "feedback": "Cette syntaxe n'existe pas en\nPython. Le mot-clé `test`\nn'est pas reconnu par le\nlangage.\n"
        }
      ],
      "explanation": "L'instruction `assert` est rapide\nà écrire et lisible. Pour des\ntests plus structurés, avec\nrapports détaillés, on préfère le\nmodule `unittest` ou la\nbibliothèque `pytest`."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "debogage-erreur"
      ],
      "title": "Erreur Python",
      "statement": "Quand Python lève une **erreur**, où\nregarder pour comprendre ce qui se\npasse ?",
      "options": [
        {
          "text": "Le code source du module Python lui-même",
          "correct": false,
          "feedback": "Ce serait à faire en dernier\nrecours. La plupart du temps,\nl'erreur vient du code\nutilisateur, pas de\nl'interpréteur.\n"
        },
        {
          "text": "Les paramètres de configuration du système d'exploitation",
          "correct": false,
          "feedback": "La trace d'erreur Python est\nlocale au programme. Le\nsystème d'exploitation n'est\npas concerné.\n"
        },
        {
          "text": "Sur Internet, en cherchant un message proche",
          "correct": false,
          "feedback": "Cette démarche peut compléter\nl'analyse, mais ce n'est pas\nla première chose à faire. Il\nfaut commencer par la trace\nd'erreur affichée par Python.\n"
        },
        {
          "text": "La trace d'erreur, qui indique le fichier et la ligne",
          "correct": true,
          "feedback": "On lit la trace d'erreur du bas\nvers le haut. La dernière ligne\ndonne en général le type\nd'erreur et le message\nassocié.\n"
        }
      ],
      "explanation": "Quelques erreurs fréquentes :\n`NameError` (variable inconnue),\n`TypeError` (incompatibilité de\ntypes), `IndexError` (indice hors\ndes bornes), `ValueError` (valeur\ninappropriée pour une opération)."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "print-debug"
      ],
      "title": "Instruction print de débogage",
      "statement": "À quoi sert un appel à `print` dans\nun objectif de **débogage** ?",
      "options": [
        {
          "text": "À empêcher d'écrire d'autres lignes de code",
          "correct": false,
          "feedback": "Cette description ne correspond\nà aucun usage réel de `print`.\n"
        },
        {
          "text": "À encoder automatiquement des données en hexadécimal",
          "correct": false,
          "feedback": "La fonction `print` ne réalise\naucune conversion automatique\nen hexadécimal.\n"
        },
        {
          "text": "À afficher la valeur d'une variable à un moment précis",
          "correct": true,
          "feedback": "C'est une technique simple mais\nefficace. Pour suivre la valeur\nde `x` à l'intérieur d'une\nboucle, on peut écrire\n`print(f\"x = {x}\")`.\n"
        },
        {
          "text": "À ralentir intentionnellement le programme",
          "correct": false,
          "feedback": "Un excès de `print` peut\neffectivement ralentir un\nprogramme, mais ce n'est jamais\nl'objectif recherché.\n"
        }
      ],
      "explanation": "Avantages : la fonction `print`\nest simple et fonctionne partout.\nInconvénient : il faut penser à\nretirer ces affichages après le\ndébogage. Pour un suivi plus\nsophistiqué, on peut utiliser le\nmodule `logging` ou un débogueur\ninteractif comme `pdb`."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "bug-definition"
      ],
      "title": "Notion de bug",
      "statement": "Qu'est-ce qu'un **bug** ?",
      "options": [
        {
          "text": "Un comportement non conforme aux spécifications",
          "correct": true,
          "feedback": "Le terme est historique : il\nprovient d'un véritable insecte\nretrouvé en $1947$ dans les\nrelais d'une machine analysée\npar Grace Hopper. La trace de\ncet incident est encore\nconservée.\n"
        },
        {
          "text": "Une fonctionnalité que l'utilisateur final n'a pas demandée",
          "correct": false,
          "feedback": "Cette description est une\nplaisanterie courante (« ce\nn'est pas un bug, c'est une\nfonctionnalité »), mais elle\nne correspond pas à la\ndéfinition technique.\n"
        },
        {
          "text": "Une nouvelle version d'un logiciel",
          "correct": false,
          "feedback": "Une nouvelle version peut\ncomporter des bugs, mais elle\nn'est pas elle-même un bug.\n"
        },
        {
          "text": "Un type particulier de mémoire",
          "correct": false,
          "feedback": "Un bug n'a aucun rapport avec\nla mémoire d'un ordinateur.\n"
        }
      ],
      "explanation": "Il faut distinguer plusieurs\nnotions : un **bug** signifie\nque le code ne fait pas ce qu'il\ndoit ; une **erreur d'exécution**\nsurvient quand le programme\nplante ; une **erreur de spécification** signifie que la\ndéfinition même du\ncomportement attendu est\nincorrecte. Cette dernière est\nla plus difficile à corriger."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "jeu-essai"
      ],
      "title": "Jeu d'essais",
      "statement": "Qu'est-ce qu'un **jeu d'essais** ?",
      "options": [
        {
          "text": "Un test choisi au hasard",
          "correct": false,
          "feedback": "Un jeu d'essais est conçu de\nmanière réfléchie, et non tiré\nau hasard.\n"
        },
        {
          "text": "Des entrées prévues avec leur sortie attendue",
          "correct": true,
          "feedback": "Un bon jeu d'essais couvre les\ncas normaux, les cas limites\n(par exemple liste vide, liste\nd'un seul élément, valeurs\nextrêmes) et les cas d'erreur\n(entrée invalide). Plus la\ncouverture est large, plus le\ntest est robuste.\n"
        },
        {
          "text": "Un manuel complet d'utilisation du programme et de ses options",
          "correct": false,
          "feedback": "La documentation et les jeux\nd'essais sont deux notions\ndistinctes.\n"
        },
        {
          "text": "Une collection de jeux vidéo",
          "correct": false,
          "feedback": "Cette interprétation littérale\ndu mot « jeu » est sans\nrapport avec son sens\ninformatique.\n"
        }
      ],
      "explanation": "Un bon jeu d'essais détecte les\nrégressions. Si l'on modifie le\ncode et que tous les tests\ncontinuent à passer, on peut\navoir confiance. Si un test\néchoue, on sait précisément quoi\ncorriger."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "exemple-debug"
      ],
      "title": "Lire une trace d'erreur",
      "statement": "```\nFile \"main.py\", line 5, in <module>\n    print(liste[5])\nIndexError: list index out of range\n```\nQue dit cette trace d'erreur ?",
      "options": [
        {
          "text": "On accède à `liste[5]`, un indice qui n'existe pas",
          "correct": true,
          "feedback": "Le message\n`IndexError: list index out of\nrange` signifie un accès à un\nindice hors des bornes. La\ncause typique est une mauvaise\nestimation de la taille de la\nliste.\n"
        },
        {
          "text": "La ligne $5$ contient une variable non définie ailleurs",
          "correct": false,
          "feedback": "Cette description correspond à\nune `NameError`, et non à une\n`IndexError`.\n"
        },
        {
          "text": "Il s'agit d'une erreur de syntaxe",
          "correct": false,
          "feedback": "Une erreur de syntaxe se\nprésente sous la forme d'une\n`SyntaxError`, et non d'une\n`IndexError`.\n"
        },
        {
          "text": "Aucun problème n'est signalé",
          "correct": false,
          "feedback": "La trace contient bien une\nerreur, indiquée par\n`IndexError`.\n"
        }
      ],
      "explanation": "Le réflexe à acquérir : lire le\n**type** de l'erreur (qui donne\nla nature du bug), puis la\n**ligne** indiquée (qui en\ndonne l'emplacement). C'est\nsystématique."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "isoler-bug"
      ],
      "title": "Isoler un bug",
      "statement": "Pour comprendre l'origine d'un bug\ndans un programme, quelle démarche\nest la plus efficace en première\napproche ?",
      "options": [
        {
          "text": "Ignorer le bug s'il n'apparaît qu'à l'occasion",
          "correct": false,
          "feedback": "Un bug intermittent, surtout,\nne s'efface pas en l'ignorant.\nIl finit par réapparaître au\nplus mauvais moment. Au\ncontraire, il faut chercher\nactivement à le reproduire.\n"
        },
        {
          "text": "Réduire l'entrée au cas le plus simple qui reproduit le bug",
          "correct": true,
          "feedback": "C'est l'idée du cas de test\nminimal. Si un programme plante\nsur une liste de mille\néléments, essayer d'abord avec\ndix, puis avec cinq, puis avec\ndeux, en gardant à chaque fois\nla propriété qui fait planter.\nOn obtient un exemple court qui\naide à localiser le bug et à\nécrire un test de régression.\n"
        },
        {
          "text": "Réécrire l'intégralité du programme depuis zéro",
          "correct": false,
          "feedback": "Cette démarche est risquée et\npeu économique. Elle introduit\nsouvent de nouveaux bugs sans\ncomprendre l'origine du bug\ninitial.\n"
        },
        {
          "text": "Multiplier la taille de l'entrée jusqu'à trouver une régularité",
          "correct": false,
          "feedback": "C'est l'inverse de la démarche\nutile. Plus l'entrée est\ngrande, plus il est difficile\nd'identifier ce qui déclenche\nprécisément le bug.\n"
        }
      ],
      "explanation": "La discipline du cas de test\nminimal est l'une des bases du\ndébogage. Elle permet aussi\nd'écrire un test de non-régression\ncompact, qui protégera contre la\nréapparition du bug à l'avenir."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "trace-erreur"
      ],
      "title": "Identifier un bug",
      "statement": "```python\ndef somme(liste):\n    total = 0\n    for i in range(1, len(liste)):\n        total += liste[i]\n    return total\n```\nQuel **bug** contient cette fonction ?",
      "options": [
        {
          "text": "Aucun bug, la fonction additionne bien tous les éléments",
          "correct": false,
          "feedback": "Erreur : pour `[1, 2, 3]`, la fonction\nrenvoie 5 et non 6. Le premier élément\nn'est jamais ajouté.\n"
        },
        {
          "text": "La fonction ne renvoie jamais la moindre valeur utile",
          "correct": false,
          "feedback": "La fonction se termine bien\npar `return total`, donc\nelle renvoie une valeur.\n"
        },
        {
          "text": "La syntaxe de `range` est incorrecte avec deux arguments",
          "correct": false,
          "feedback": "Erreur : `range(1, n)` est valide (début\ninclus, fin exclue). Le code s'exécute\nsans erreur, le bug est purement logique.\n"
        },
        {
          "text": "Le premier élément, `liste[0]`, n'est jamais ajouté",
          "correct": true,
          "feedback": "Bonne réponse : la boucle commence à\nl'indice $1$ au lieu de $0$. C'est un bug\nclassique de **décalage d'indice**. Pour\nla liste `[1, 2, 3]`, la\nfonction renvoie $5$ au lieu\nde $6$. Un test sur un cas\nsimple permettrait de le\ndétecter.\n"
        }
      ],
      "explanation": "Les bugs de décalage d'indice\nsont très fréquents. Il faut\ntoujours tester avec des listes\nsimples (`[]`, `[1]`,\n`[1, 2]`) pour les détecter.\nUn bon test serait par exemple\n`assert somme([1, 2, 3]) == 6`."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "traceback-lecture"
      ],
      "title": "Lire la trace d'erreur",
      "statement": "Dans une trace d'erreur à plusieurs\nniveaux, où trouve-t-on en général\nla **cause initiale** du bug ?",
      "options": [
        {
          "text": "Quelque part au milieu de la trace d'erreur affichée",
          "correct": false,
          "feedback": "Aucune position particulière\nau milieu n'est privilégiée.\nLa cause initiale se lit en\nbas de la trace.\n"
        },
        {
          "text": "Au hasard dans la trace",
          "correct": false,
          "feedback": "La position du message\nd'erreur n'a rien d'aléatoire :\nil se trouve toujours à la\nfin de la trace.\n"
        },
        {
          "text": "En haut de la trace",
          "correct": false,
          "feedback": "C'est plutôt l'inverse. Le\nmessage d'erreur le plus\nprécis se trouve en bas de\nla trace.\n"
        },
        {
          "text": "La dernière ligne (en bas), avec le type d'erreur",
          "correct": true,
          "feedback": "Python affiche la pile des\nappels de fonctions dans\nl'ordre chronologique, et le\nmessage d'erreur tout en\nbas. La ligne la plus utile\nest généralement celle qui\nrenvoie à votre propre code.\n"
        }
      ],
      "explanation": "Pour lire une trace : commencer\npar la fin, qui indique le type\nd'erreur, puis remonter\njusqu'au code qu'on a soi-même\nécrit. Les premières lignes,\nsouvent dans des bibliothèques,\nsont rarement le bon point\nd'entrée."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "reproductibilite"
      ],
      "title": "Reproduire un bug",
      "statement": "Pour résoudre un bug, qu'est-ce qui\nest **essentiel** à faire en\npremier ?",
      "options": [
        {
          "text": "Demander à un collègue de regarder le code",
          "correct": false,
          "feedback": "Cela peut aider dans un\nsecond temps, mais ni un\nhumain ni un assistant\nautomatique ne peuvent\ndiagnostiquer un bug sans\npouvoir le reproduire.\n"
        },
        {
          "text": "Réécrire le code suspect",
          "correct": false,
          "feedback": "La réécriture n'est pas la\npremière étape. Il faut\nd'abord pouvoir observer le\nbug pour pouvoir vérifier\nensuite la correction.\n"
        },
        {
          "text": "Réinstaller l'interpréteur Python",
          "correct": false,
          "feedback": "Cette opération est rarement\nnécessaire. La cause d'un\nbug se trouve presque\ntoujours dans le code lui-même.\n"
        },
        {
          "text": "Le reproduire avec une entrée minimale fiable",
          "correct": true,
          "feedback": "Un bug intermittent est très\ndifficile à corriger. La\nreproduction est la première\nétape, et permet d'écrire un\n**test de régression**.\n"
        }
      ],
      "explanation": "L'adage classique est :\n« si l'on ne peut pas\nreproduire le bug, on ne peut\npas le corriger ». Une fois la\nreproduction obtenue, on peut\nréduire l'entrée au cas minimal\nqui déclenche encore le bug,\npuis corriger."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "debogueur"
      ],
      "title": "Débogueur",
      "statement": "Qu'est-ce qu'un **débogueur** ?",
      "options": [
        {
          "text": "Un compilateur qui traduit le code source en exécutable",
          "correct": false,
          "feedback": "Un compilateur transforme du\ncode source en code\nexécutable. Il n'a aucun\nrapport avec le débogage\ninteractif.\n"
        },
        {
          "text": "Un outil qui exécute pas à pas et montre les variables",
          "correct": true,
          "feedback": "Bonne réponse : on avance ligne par\nligne et on inspecte l'état du\nprogramme. Plusieurs outils existent :\n`pdb` en ligne de commande,\nou les débogueurs intégrés\naux environnements de\ndéveloppement comme VS Code\nou PyCharm. Tous sont\npuissants pour comprendre\ndes bugs complexes.\n"
        },
        {
          "text": "Un antivirus qui détecte les programmes malveillants",
          "correct": false,
          "feedback": "Un antivirus protège contre\nles logiciels malveillants ;\nil n'a rien à voir avec le\ndébogage de programmes.\n"
        },
        {
          "text": "Un fichier de journalisation des erreurs du programme",
          "correct": false,
          "feedback": "Un débogueur est un outil\ninteractif, et non un fichier\nstocké sur le disque.\n"
        }
      ],
      "explanation": "Pour activer `pdb`, on lance la\ncommande `python -m pdb\nscript.py`, ou on insère\n`breakpoint()` dans le code\n(depuis Python 3.7). Quelques\ncommandes utiles : `n` pour\npasser à la ligne suivante,\n`s` pour entrer dans une\nfonction, `c` pour continuer,\net `p variable` pour afficher\nla valeur d'une variable."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "test-couverture"
      ],
      "title": "Couverture de tests",
      "statement": "Que mesure la **couverture de tests** ?",
      "options": [
        {
          "text": "La part des lignes du code exécutées par les tests",
          "correct": true,
          "feedback": "C'est un indicateur de\nqualité des tests. Une\ncouverture de $80 \\%$ est\nbien meilleure qu'une\ncouverture de $30 \\%$.\nL'outil `coverage.py`\npermet de la mesurer en\nPython.\n"
        },
        {
          "text": "La taille en octets du programme",
          "correct": false,
          "feedback": "La taille du programme n'a\naucun rapport avec la\ncouverture des tests.\n"
        },
        {
          "text": "Le nombre total de tests écrits",
          "correct": false,
          "feedback": "La couverture ne se réduit\npas à un comptage des tests.\nElle s'intéresse à la part\ndu code effectivement\nparcourue.\n"
        },
        {
          "text": "La performance globale d'exécution du programme testé",
          "correct": false,
          "feedback": "La couverture n'a rien à\nvoir avec la performance.\nC'est une mesure de la\nqualité de la suite de\ntests.\n"
        }
      ],
      "explanation": "Attention : une couverture de\n$100 \\%$ ne garantit pas\nl'absence de bugs. On peut\npasser dans toutes les lignes\nsans pour autant tester tous\nles **comportements**. C'est\nnéanmoins une base utile."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "regression"
      ],
      "title": "Test de régression",
      "statement": "Qu'est-ce qu'un **test de régression** ?",
      "options": [
        {
          "text": "Un test qui revient en arrière dans le code",
          "correct": false,
          "feedback": "Cette interprétation\nlittérale du mot\n« régression » ne\ncorrespond pas au sens\ntechnique de\nl'expression.\n"
        },
        {
          "text": "Un test qui échoue à chaque exécution",
          "correct": false,
          "feedback": "Un test de régression doit\nau contraire passer après\nla correction. S'il échoue\nsystématiquement, c'est que\nla correction n'a pas été\nfaite, ou qu'elle a été\ndéfaite.\n"
        },
        {
          "text": "Un test ajouté après correction d'un bug",
          "correct": true,
          "feedback": "C'est une pratique\nessentielle. Pour chaque\nbug corrigé, il est\nrecommandé d'ajouter un\ntest qui aurait permis de\nle détecter. Cela protège\ncontre les régressions\nfutures.\n"
        },
        {
          "text": "Un test sur l'ancienne version du code",
          "correct": false,
          "feedback": "Un test de régression\ns'exécute sur la version\nactuelle du code, pas sur\nune ancienne. Son rôle est\nde prévenir le retour d'un\nbug ancien.\n"
        }
      ],
      "explanation": "Une discipline classique\nconsiste en un cycle « rouge\npuis vert » : on écrit\nd'abord un test qui reproduit\nle bug et qui échoue (état\n« rouge »), puis on corrige\nle code jusqu'à ce que le\ntest passe (état « vert »)."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "tests-en-amont"
      ],
      "title": "Écrire les tests avant le code",
      "statement": "Quel est l'avantage principal\nd'écrire les tests **avant** le\ncode (approche dite de\ndéveloppement piloté par les\ntests) ?",
      "options": [
        {
          "text": "Elle force à clarifier la spécification avant de coder",
          "correct": true,
          "feedback": "On écrit la spécification\nsous une forme exécutable\n(le test), ce qui contraint\nà réfléchir précisément\naux comportements attendus\navant de coder.\n"
        },
        {
          "text": "Le code est plus rapide à écrire dès le départ",
          "correct": false,
          "feedback": "Cette approche n'est pas\nplus rapide au démarrage,\net exige même un certain\ntemps d'apprentissage. Son\nintérêt se mesure surtout\nà moyen terme, sur la\nqualité et la fiabilité\ndu code produit.\n"
        },
        {
          "text": "Elle dispense totalement d'avoir à écrire des commentaires",
          "correct": false,
          "feedback": "Aucun rapport. Les\ncommentaires gardent leur\nutilité pour expliquer\nles intentions et les\nchoix de conception.\n"
        },
        {
          "text": "Elle permet d'éviter complètement les bugs",
          "correct": false,
          "feedback": "Cette affirmation est trop\noptimiste. Aucune méthode\nne supprime tous les bugs.\nLes tests réduisent leur\nfréquence et facilitent\nleur détection précoce.\n"
        }
      ],
      "explanation": "Le cycle est le suivant : on\nécrit d'abord un test qui\néchoue ; on écrit ensuite le\ncode minimal qui le fait\npasser ; on retravaille\nenfin la structure du code,\nen conservant les tests\nverts. Ce cycle se répète\nsur de petites unités, à un\nrythme fréquent."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "bug-difficile"
      ],
      "title": "Bug intermittent",
      "statement": "Un bug se produit **parfois**,\nsans que l'on sache prévoir\nquand. Que faire ?",
      "options": [
        {
          "text": "Multiplier les journaux et comparer les exécutions",
          "correct": true,
          "feedback": "Les bugs intermittents\nrévèlent souvent des\ncourses critiques, des\nproblèmes de\nsynchronisation, ou des\ndépendances cachées entre\nparties du programme.\n"
        },
        {
          "text": "Relancer le programme jusqu'à ce que le bug ne se produise plus",
          "correct": false,
          "feedback": "Erreur : si le bug ne se\nmanifeste pas lors d'une\nexécution donnée, il n'a pas\ndisparu pour autant. Sans\ndiagnostic de la cause, il\nfinira par resurgir.\n"
        },
        {
          "text": "Ralentir l'exécution avec des appels à `time.sleep()`",
          "correct": false,
          "feedback": "Erreur : ralentir peut faire\ndisparaître le symptôme\n(typique d'un problème de\nsynchronisation) sans corriger\nla cause. Le bug reste présent\net resurgira dans d'autres\nconditions.\n"
        },
        {
          "text": "Ignorer le bug",
          "correct": false,
          "feedback": "C'est une mauvaise pratique.\nUn bug ignoré finit\ntoujours par revenir, et\nsouvent au pire moment.\n"
        }
      ],
      "explanation": "Causes typiques\nd'intermittence : exécution\nconcurrente (fils\nd'exécution, parallélisme),\nmémoire non initialisée,\nétat global partagé, ordre\ndes éléments dans un\ndictionnaire (avant Python\n3.7)."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "debug-strategie"
      ],
      "title": "Stratégie globale",
      "statement": "Devant un bug, quelle est la\n**bonne stratégie** générale à\nadopter ?",
      "options": [
        {
          "text": "Demander une mise à jour du langage Python",
          "correct": false,
          "feedback": "Une telle mise à jour est\nextrêmement rarement la\nsolution à un bug\napplicatif.\n"
        },
        {
          "text": "Reproduire, isoler, corriger, puis ajouter un test",
          "correct": true,
          "feedback": "C'est une méthodologie\néprouvée. Sauter une étape,\nen particulier la dernière,\ngarantit que le bug\nfinira par réapparaître.\n"
        },
        {
          "text": "Réécrire intégralement le programme",
          "correct": false,
          "feedback": "Cette démarche est risquée :\nelle peut introduire de\nnouveaux bugs et masque\nl'origine du problème\ninitial.\n"
        },
        {
          "text": "Désactiver les tests existants",
          "correct": false,
          "feedback": "Cette démarche est\ncontre-productive. Les\ntests sont précisément\nl'outil principal pour\ndétecter et corriger les\nbugs.\n"
        }
      ],
      "explanation": "Discipline : ne jamais\ncorriger un bug sans avoir\nréussi à le reproduire, et\nne jamais corriger sans\najouter ensuite un test de\nrégression. Sinon, le bug\nfinira par réapparaître,\net le temps gagné au\ndépart sera perdu plus\ntard."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "piege-tests"
      ],
      "title": "Test mal écrit",
      "statement": "Quel est le problème avec ce\ntest ?\n```python\nassert chercher([1, 2, 3], 2)\n```",
      "options": [
        {
          "text": "La syntaxe de `assert` est invalide sans message d'erreur",
          "correct": false,
          "feedback": "Erreur : le message après la virgule est\nfacultatif, `assert expression` seul est\nvalide. Le défaut est sémantique : le test\nn'est pas assez précis.\n"
        },
        {
          "text": "La fonction `chercher` n'existe peut-être pas encore",
          "correct": false,
          "feedback": "On suppose que la fonction\nexiste et est définie\nquelque part dans le code.\nLe problème ne tient pas\nà son existence.\n"
        },
        {
          "text": "Aucun problème, le test vérifie que 2 est trouvé à l'indice 1",
          "correct": false,
          "feedback": "Erreur : le test ne compare jamais la\nvaleur renvoyée à 1. Il passe pour tout\nrésultat « vrai », par exemple 2 ou `True`.\n"
        },
        {
          "text": "Il vérifie seulement que le résultat est « vrai »",
          "correct": true,
          "feedback": "Bonne réponse : ce test est trop laxiste.\nTout résultat vrai au sens booléen (1, 2,\n`True`...) le fait passer, indice faux\ncompris. On préfère écrire\n`assert chercher([1, 2, 3],\n2) == 1`, qui vérifie\nprécisément la valeur\nrenvoyée.\n"
        }
      ],
      "explanation": "Piège des tests laxistes :\nils passent quand ils ne\ndevraient pas, et laissent\npasser des bugs. Il faut\ntoujours **vérifier des valeurs précises**, et non\nsimplement l'absence\nd'erreur."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "test-vs-debug"
      ],
      "title": "Tests et débogage",
      "statement": "Quelle est la\n**complémentarité** entre tests\net débogage ?",
      "options": [
        {
          "text": "L'une remplace l'autre",
          "correct": false,
          "feedback": "Ces activités sont\ncomplémentaires : ni les\ntests ni le débogage ne\npeuvent se passer l'un de\nl'autre dans un projet\nsérieux.\n"
        },
        {
          "text": "Les tests trouvent les bugs, le débogage les corrige",
          "correct": true,
          "feedback": "Ce sont deux phases\ndistinctes mais\ncomplémentaires. Tester,\nc'est investir pour\ndétecter ; déboguer, c'est\ninvestiguer pour corriger.\n"
        },
        {
          "text": "Le débogage permet d'éviter complètement d'écrire des tests",
          "correct": false,
          "feedback": "C'est l'inverse : un bon\nensemble de tests permet\nde détecter rapidement un\nbug et de réduire le\ntemps passé à le déboguer.\n"
        },
        {
          "text": "Les deux activités sont identiques",
          "correct": false,
          "feedback": "Ces activités diffèrent\npar leur objectif et leur\ndéroulement, comme\nexpliqué dans la bonne\nréponse.\n"
        }
      ],
      "explanation": "Une démarche idéale combine\nles deux activités : on\nécrit d'abord des tests qui\ntrouvent un bug ; on\ndébogue ensuite pour\nlocaliser, comprendre et\ncorriger ; on ajoute enfin\nun test de régression pour\nempêcher la résurgence du\nbug."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "statement": "Parmi les affirmations suivantes\nsur les tests et le débogage,\nlaquelle est **fausse** ?",
      "options": [
        {
          "text": "Réduire l'entrée problématique au cas le plus simple qui reproduit le bug aide à localiser sa cause",
          "correct": false,
          "feedback": "Cette affirmation est\ncorrecte. C'est l'une des\ntechniques de base pour\nisoler un bug.\n"
        },
        {
          "text": "La trace d'erreur indique le type d'erreur et la ligne où elle s'est produite",
          "correct": false,
          "feedback": "Cette affirmation est\ncorrecte. C'est même son\nrôle principal.\n"
        },
        {
          "text": "Un test unitaire vérifie une seule fonction de manière isolée",
          "correct": false,
          "feedback": "Cette affirmation est\ncorrecte. C'est même la\ndéfinition d'un test\nunitaire.\n"
        },
        {
          "text": "Les tests garantissent l'absence de bugs",
          "correct": true,
          "feedback": "Cette affirmation est\nfausse (donc c'est la\nbonne réponse). Les\ntests révèlent la\n**présence** de bugs sur\nles cas effectivement\ntestés, mais ils ne\npeuvent jamais prouver\nl'**absence** de bugs sur\ndes entrées non testées.\nEdsger Dijkstra a résumé\ncela par : « Tester ne\npeut prouver l'absence\nde bugs ».\n"
        }
      ],
      "explanation": "Citation d'Edsger Dijkstra :\n« Program testing can be\nused to show the presence of\nbugs, but never to show\ntheir absence. » Pour\ngarantir formellement\nl'absence de bugs, il\nfaudrait recourir à une\npreuve mathématique, ce qui\nreste rarement praticable\nsur du code réel."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "types-erreurs"
      ],
      "title": "Trois familles d'erreurs",
      "statement": "On distingue habituellement les\nerreurs de **syntaxe**, les\nerreurs **d'exécution** et les\nerreurs **sémantiques** (ou\nlogiques). Quelle catégorie est\nla plus difficile à détecter\nautomatiquement ?",
      "options": [
        {
          "text": "Les erreurs de syntaxe, car elles cassent toute l'exécution",
          "correct": false,
          "feedback": "Erreur : ces erreurs sont au\ncontraire les **plus faciles** à détecter. Python\nrefuse de lancer le programme\net affiche immédiatement\n`SyntaxError`, avec le\nnuméro de ligne. Aucune\nmystère.\n"
        },
        {
          "text": "Les erreurs d'exécution, car le programme s'arrête en cours de route",
          "correct": false,
          "feedback": "Erreur : ces erreurs sont\naussi facilement repérables.\nLe programme s'arrête en\nlevant une exception, avec\nune trace d'erreur précise\n(`IndexError`,\n`ZeroDivisionError`, etc.).\nOn voit immédiatement où\nchercher.\n"
        },
        {
          "text": "Les erreurs sémantiques, le programme tourne mais est faux",
          "correct": true,
          "feedback": "Bonne réponse : c'est le bug\nle plus pernicieux. Aucun\nmessage d'erreur, juste un\nrésultat faux. Seuls des\ntests bien choisis ou une\nrelecture attentive\npermettent de le détecter.\nExemple : une boucle qui\ncommence à l'indice 1 au\nlieu de 0.\n"
        },
        {
          "text": "Les trois catégories d'erreurs sont équivalentes",
          "correct": false,
          "feedback": "Erreur : ces trois\ncatégories diffèrent\nradicalement par leur mode\nde détection. La syntaxe est\ndétectée par l'analyseur\nsyntaxique, l'exécution par\nl'interpréteur, la sémantique\nuniquement par les tests ou\nla relecture humaine.\n"
        }
      ],
      "explanation": "Mémorable : « ce qui ne\ncompile pas, ne s'exécute\npas ; ce qui s'exécute peut\nplanter ; ce qui ne plante\npas peut quand même être\nfaux ». Les tests servent\nsurtout à détecter la\ntroisième catégorie."
    },
    {
      "id": "q27",
      "difficulty": 2,
      "skills": [
        "tests-multiples"
      ],
      "title": "Plusieurs cas dans un test",
      "statement": "Comment écrire un test qui\nvérifie le comportement d'une\nfonction `carre` sur **plusieurs entrées** sans dupliquer le code\n?",
      "options": [
        {
          "text": "```python\ndef test_carre():\n    return carre(0) and carre(1) and carre(3)\n```\n",
          "correct": false,
          "feedback": "Erreur : ce code ne vérifie\npas que les valeurs\nrenvoyées sont\n**correctes**. Il vérifie\nseulement qu'aucune n'est\nfausse au sens booléen.\nComme `carre(0) == 0`,\nl'expression renvoie `0`\n(faux) sans que cela\nsignale un bug.\n"
        },
        {
          "text": "```python\ndef test_carre():\n    assert carre(0) == 0\n    assert carre(1) == 1\n    assert carre(3) == 9\n    assert carre(-2) == 4\n```\n",
          "correct": false,
          "feedback": "Cette version fonctionne\naussi, mais elle répète la\nmême logique de test à\nchaque ligne, ce que\nl'énoncé demandait\nprécisément d'éviter. La\nversion paramétrée factorise\nce schéma et indique, en cas\nd'échec, la valeur fautive.\n"
        },
        {
          "text": "```python\ndef test_carre():\n    for x, attendu in [(0, 0), (1, 1), (3, 9), (-2, 4)]:\n        assert carre(x) == attendu, f\"échec pour x = {x}\"\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : on parcourt\nune liste de couples\n`(entrée, sortie attendue)`,\non vérifie chaque cas avec\n`assert`. Le message après\nla virgule indique\nprécisément quel cas a\néchoué, ce qui facilite le\ndébogage. C'est aussi le\nprincipe des **tests paramétrés** de `pytest`.\n"
        },
        {
          "text": "```python\ndef test_carre():\n    for x, attendu in [(0, 0), (1, 1), (3, 9), (-2, 4)]:\n        assert carre(x), attendu\n```\n",
          "correct": false,
          "feedback": "Erreur : la virgule n'est pas une\ncomparaison. `assert carre(x), attendu`\nvérifie seulement que `carre(x)` est vrai\net utilise `attendu` comme message\nd'échec : le test échoue pour x = 0 (car 0\nest faux) et ne compare jamais la valeur\nrenvoyée à la valeur attendue.\n"
        }
      ],
      "explanation": "Schéma général d'un test\nparamétré : une liste de\ncouples `(entrée, sortie\nattendue)`, parcourus dans\nune boucle qui vérifie\nchaque cas. La bibliothèque\n`pytest` formalise cela avec\nle décorateur\n`@pytest.mark.parametrize`."
    }
  ]
}