Adloun

NSI (première) · Chapitre 11 · Exercice de TD

Une fonction corrige un questionnaire à choix unique

Conduire un projet · Tester pour de vrai · niveau 3 (difficile)

Énoncé

Une fonction corrige un questionnaire à choix unique.


def score(reponses, corrige):
    """Un point par bonne reponse."""
    n = 0
    for i in range(len(reponses)):
        if reponses[i] == corrige[i]:
            n = n + 1
    return n

Un élève signale que le programme plante quand il coche plus de réponses qu'il n'y a de questions. Un camarade corrige en remplaçant la ligne 4 par for i in range(len(corrige)):.

  • Vérifier que la correction règle le défaut signalé.
  • Montrer qu'elle en introduit un autre, qui n'existait pas avant.
  • Proposer une version correcte, et le jeu de tests qui interdit aux deux défauts de revenir.

Corrigé

Posons corrige = ["a", "b", "c", "d"], soit quatre questions.

1. Avec la version d'origine, score(["a","b","c","d","e"], corrige) parcourt de à et évalue corrige[4] : IndexError. Avec range(len(corrige)), la boucle s'arrête à et la fonction renvoie . Le défaut signalé est bien réglé.

2. Mais le cas inverse fonctionnait, et ne fonctionne plus. Un élève qui ne répond qu'aux deux premières questions :

score(2 réponses)score(5 réponses)
version d'origineIndexError
version corrigéeIndexError
version correcte

On a déplacé l'erreur d'un cas à l'autre. C'est une régression : la correction d'un défaut a cassé un comportement qui marchait.

3. La boucle doit s'arrêter au plus court des deux :


def score(reponses, corrige):
    """Un point par bonne reponse.
    Les reponses en trop sont ignorees, les manquantes valent faux."""
    n = 0
    for i in range(min(len(reponses), len(corrige))):
        if reponses[i] == corrige[i]:
            n = n + 1
    return n

def test_non_regression():
    corrige = ["a", "b", "c", "d"]
    assert score(corrige, corrige) == 4              # tout juste
    assert score(["a", "x", "c", "x"], corrige) == 2 # cas ordinaire
    assert score(["a", "b"], corrige) == 2           # trop peu de reponses
    assert score(["a", "b", "c", "d", "e"], corrige) == 4  # trop de reponses
    assert score([], corrige) == 0                   # aucune reponse

test_non_regression()
print("ok test_non_regression")

Ce que ce jeu de tests garantit, et qu'aucun des deux groupes n'avait. Les deux derniers cas sont les deux défauts, l'ancien et le nouveau, transformés en assertions. Ils ne peuvent plus revenir sans être vus. C'est la troisième habitude du cours — écrire le test avant la correction — appliquée à la lettre : on aurait écrit assert score(["a","b","c","d","e"], corrige) == 4 d'abord, on l'aurait vu échouer, et le jeu de tests complet aurait alors immédiatement révélé que la correction cassait le cas court.

Une remarque de spécification. La docstring dit maintenant ce que la fonction fait des listes de longueurs différentes. C'est une décision, pas une évidence : on aurait pu choisir de refuser l'appel par un assert. Le défaut initial venait moins du code que de l'absence de cette décision — personne ne s'était demandé ce que devait faire le programme dans ce cas, donc chacun a corrigé selon l'exemple qu'il avait sous les yeux.

100 % gratuit

Essaie le suivant avec le tuteur

Faire l'exercice suivant avec le tuteur

Sans compte : le cours, 2 minutes de podcast et de vidéo et un exercice avec le tuteur. Pour tout le reste : inscription gratuite en 30 secondes, sans carte bancaire.

Le cours du chapitre · Tous les exercices du chapitre

Exercice suivant avec le tuteur →