{
  "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 qu'un programme se comporte comme attendu sur des entrées choisies",
          "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",
          "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 exception `AssertionError` ; sinon, elle ne fait rien",
          "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 booléen",
          "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 tout le 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 le comportement d'une seule fonction (une « unité » de code) sur des entrées spécifiques",
          "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 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 affichée par Python, qui indique le fichier, la ligne concernée et le type d'erreur",
          "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 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 du programme, pour suivre son déroulement",
          "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": "Une erreur ou un comportement non conforme aux spécifications du programme",
          "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 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\nspé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": "Un ensemble d'entrées prévues, chacune accompagnée de sa sortie attendue, qui couvre les cas typiques et les cas limites de la fonction",
          "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 d'utilisation du programme",
          "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": "À la ligne $5$ de `main.py`, on tente d'accéder à `liste[5]` alors que cet indice n'existe pas (la liste est trop courte)",
          "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",
          "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 problématique au cas le plus simple qui reproduit encore 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": "q11",
      "difficulty": 2,
      "skills": [
        "unittest"
      ],
      "title": "Module unittest",
      "statement": "Quel module de la bibliothèque\nstandard de Python permet d'écrire\ndes **tests structurés** ?",
      "options": [
        {
          "text": "`debug`",
          "correct": false,
          "feedback": "Aucun module de ce nom\nn'existe dans la bibliothèque\nstandard de Python.\n"
        },
        {
          "text": "`unittest`",
          "correct": true,
          "feedback": "C'est le module standard pour\nles tests unitaires en\nPython. On définit des\nclasses héritant de\n`TestCase` et l'on écrit des\nméthodes dont le nom commence\npar `test_`. Une alternative\ntrès populaire est la\nbibliothèque `pytest`, plus\nsimple d'usage.\n"
        },
        {
          "text": "`assert_module`",
          "correct": false,
          "feedback": "Aucun module de ce nom\nn'existe dans la bibliothèque\nstandard.\n"
        },
        {
          "text": "`tests`",
          "correct": false,
          "feedback": "Aucun module de ce nom\nn'existe non plus dans la\nbibliothèque standard.\n"
        }
      ],
      "explanation": "Pour exécuter les tests, on\nutilise la commande `python -m\nunittest`. Pour un débutant, des\n`assert` directs peuvent\nsuffire. Pour un projet\nsérieux, mieux vaut adopter\n`unittest` ou `pytest`."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "pytest"
      ],
      "title": "La bibliothèque pytest",
      "statement": "Pourquoi la bibliothèque `pytest`\nest-elle si populaire ?",
      "options": [
        {
          "text": "Pour sa syntaxe simple (un `assert valeur == attendu` suffit, pas besoin de classes), ses fixtures et son écosystème de greffons",
          "correct": true,
          "feedback": "Cette bibliothèque simplifie\nconsidérablement l'écriture\ndes tests par rapport au\nmodule `unittest`. Elle est\ndevenue le standard de fait\ndans le monde Python\nmoderne.\n"
        },
        {
          "text": "Parce qu'elle est obligatoire dans tout projet Python",
          "correct": false,
          "feedback": "Aucune obligation n'existe.\nLa bibliothèque `pytest` est\nfacultative, même si très\nrecommandée.\n"
        },
        {
          "text": "Parce qu'elle génère automatiquement le code des tests",
          "correct": false,
          "feedback": "La bibliothèque `pytest` ne\ngénère pas automatiquement le\ncode des tests : c'est au\ndéveloppeur de les écrire.\n"
        },
        {
          "text": "Parce qu'elle est plus rapide à l'exécution",
          "correct": false,
          "feedback": "La rapidité d'exécution n'est\npas son atout principal. Sa\nforce vient de la simplicité\nd'écriture des tests.\n"
        }
      ],
      "explanation": "Un test minimal écrit avec\n`pytest` :\n\n```python\ndef test_carre():\n    assert carre(3) == 9\n```\n\nPour lancer les tests, on\nexécute simplement la commande\n`pytest`, qui découvre\nautomatiquement les fonctions\ndont le nom commence par\n`test_`."
    },
    {
      "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 n'est présent",
          "correct": false,
          "feedback": "Un bug existe bien dans cette\nfonction, comme expliqué dans\nla bonne réponse.\n"
        },
        {
          "text": "La fonction ne renvoie aucune valeur",
          "correct": false,
          "feedback": "La fonction se termine bien\npar `return total`, donc\nelle renvoie une valeur.\n"
        },
        {
          "text": "La syntaxe est incorrecte",
          "correct": false,
          "feedback": "Le code est syntaxiquement\nvalide. Le bug est purement\nlogique.\n"
        },
        {
          "text": "La boucle commence à l'indice $1$ au lieu de $0$, donc l'élément `liste[0]` est ignoré",
          "correct": true,
          "feedback": "C'est un bug classique de\n**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": "Au milieu de la trace",
          "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), qui indique le type et le message d'erreur, et la ligne immédiatement au-dessus, qui pointe l'endroit dans votre code",
          "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 : trouver une entrée minimale qui déclenche systématiquement le bug",
          "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",
          "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 permet d'exécuter pas à pas, d'inspecter les variables, de poser des points d'arrêt et de remonter la pile des appels",
          "correct": true,
          "feedback": "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",
          "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",
          "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\ntests** ?",
      "options": [
        {
          "text": "La proportion de lignes (ou de branches) du code qui sont effectivement 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 d'exécution du programme",
          "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\nré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 avoir corrigé un bug, dont le rôle est de vérifier que ce bug ne réapparaisse pas dans le futur",
          "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": "q19",
      "difficulty": 2,
      "skills": [
        "logging"
      ],
      "title": "Module logging",
      "statement": "Pourquoi préfère-t-on le module\n`logging` à la fonction `print`\npour le débogage en production ?",
      "options": [
        {
          "text": "Parce que la fonction `print` n'existe pas en Python",
          "correct": false,
          "feedback": "La fonction `print` existe\nbel et bien et reste utile,\nnotamment pour\nl'apprentissage.\n"
        },
        {
          "text": "Pour pouvoir activer ou désactiver les messages, choisir leur niveau de gravité (`debug`, `info`, `warning`, `error`), les rediriger vers un fichier, etc.",
          "correct": true,
          "feedback": "Le module `logging` est\nconçu pour la production.\nIl offre une configuration\ncentralisée, plusieurs\nniveaux de gravité, un\nformatage riche et plusieurs\ndestinataires possibles\n(console, fichier, réseau).\n"
        },
        {
          "text": "Parce que le module `logging` est obligatoire",
          "correct": false,
          "feedback": "L'usage du module est\nfacultatif, mais il est\nfortement recommandé pour\nles programmes destinés à\nla production.\n"
        },
        {
          "text": "Pour gagner de l'espace en mémoire",
          "correct": false,
          "feedback": "L'avantage du module\n`logging` n'est pas une\néconomie de mémoire.\nC'est avant tout une\nquestion de souplesse et\nde configurabilité.\n"
        }
      ],
      "explanation": "Exemple d'utilisation typique :\n\n```python\nimport logging\nlogging.basicConfig(level=logging.DEBUG)\nlogging.debug(\"valeur de x : %s\", x)\n```\n\nCe module est plus puissant\nque `print`, au prix d'un\nléger surcoût syntaxique."
    },
    {
      "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 oblige à clarifier la spécification avant l'implémentation, et garantit que la fonction sera testable et fera bien ce qui est attendu",
          "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 d'é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 d'exécution, ajouter des assertions, et étudier ce qui change entre les exécutions où le programme fonctionne et celles où il échoue (synchronisation, ordre, données d'entrée)",
          "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": "Réinstaller Python",
          "correct": false,
          "feedback": "Cette opération est très\nrarement la cause, et ne\ndevrait être tentée qu'en\ndernier recours.\n"
        },
        {
          "text": "Acheter un nouvel ordinateur",
          "correct": false,
          "feedback": "Cette interprétation\nlittérale est sans rapport\navec le débogage.\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 le bug, isoler la cause (en réduisant l'entrée au cas minimal), corriger, puis ajouter un test de régression",
          "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 est invalide",
          "correct": false,
          "feedback": "Le code est syntaxiquement\nvalide. Le défaut est\npurement sémantique : le\ntest n'est pas assez\nprécis.\n"
        },
        {
          "text": "La fonction `chercher` n'existe pas",
          "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 particulier",
          "correct": false,
          "feedback": "Ce test contient en réalité\nun défaut subtil mais\nimportant, comme expliqué\ndans la bonne réponse.\n"
        },
        {
          "text": "Il vérifie seulement que `chercher` renvoie une valeur considérée comme vraie au sens booléen, mais pas que l'indice est correct. La fonction pourrait renvoyer $0$ (qui est le bon indice mais évalue à `False`) ou $999$ (qui est faux, mais évalue à `True`)",
          "correct": true,
          "feedback": "Ce test est trop laxiste.\nOn 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\nvaleurs 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 (sur les cas testés), le débogage localise et corrige un bug déjà repéré",
          "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 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\nfaciles** à 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, car le programme s'exécute sans planter, mais ne fait pas ce qu'il devrait faire",
          "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 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\nentré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 l'énoncé\ndemandait précisément\nd'éviter la duplication. De\nplus, ici, dès qu'un\n`assert` échoue, les\nsuivants ne sont pas\nexécutés et l'on ignore\nquels autres cas posent\nproblème.\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\nparamétrés** de `pytest`.\n"
        },
        {
          "text": "```python\ndef test_carre():\n    assert carre == 9\n```\n",
          "correct": false,
          "feedback": "Erreur : on compare la\nfonction elle-même (un\nobjet) à 9, ce qui est\ntoujours faux. Il faut\nappeler la fonction avec\ndes paramètres et comparer\nson **résultat**.\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`."
    }
  ]
}