Adloun

Trois fonctions prétendent calculer la moyenne d'un tableau

Exercice d'entraînement · niveau 3 (difficile) · NSI (première), chapitre 3 — Langages et programmation · Mettre au point : choisir les cas qui séparent

Énoncé

Trois fonctions prétendent calculer la moyenne d'un tableau :


def moyenne_a(t):  return sum(t) / len(t)
def moyenne_b(t):
    s = 0
    for x in t:
        s = s + x
    return s // len(t)
def moyenne_c(t):
    s = 0.0
    for x in t:
        s = s + x
    return s / len(t)

Le cas de test [10, 12, 14] les déclare toutes les trois correctes. Trouver le cas le plus court qui en démasque une, et le cas qui les fait toutes échouer. Conclure sur ce qu'est un « bon » cas de test.

Corrigé

Le cas qui ne sépare rien. Sur [10, 12, 14], les trois rendent : la moyenne tombe sur un entier. Un cas de test dont le résultat attendu est « rond » est souvent un mauvais cas — il masque les erreurs de type et d'arrondi.

Le cas qui démasque moyenne_b : [10, 11]. Attendu ; moyenne_a et moyenne_c rendent , moyenne_b rend . La faute est l'opérateur //, division entière : elle tronque. Deux éléments suffisent, et c'est le plus court possible — sur un seul élément, la moyenne est l'élément lui-même et // ne se voit pas si cet élément est entier.

Le cas qui les fait toutes échouer : le tableau vide. Les trois lèvent ZeroDivisionError. Ce n'est pas un défaut des fonctions mais une précondition manquante : aucune ne déclare assert len(t) > 0. Le correctif n'est pas de rendre — ce serait mentir — mais d'ajouter l'assertion, comme la fonction moyenne du cours.

Ce qu'est un bon cas de test. Un cas est bon quand il sépare : quand il donne des résultats différents selon que la fonction est correcte ou non. Un cas qui passe sur toutes les implémentations imaginables, justes comme fausses, ne coûte rien mais n'apprend rien. Devant un jeu de tests, la bonne question n'est pas « combien de cas ? » mais « quelle erreur chaque cas est-il capable d'attraper ? » — et si la réponse est « aucune », le cas est à remplacer.

Reste moyenne_c, subtilement différente de moyenne_a : elle accumule dans un flottant dès le départ. Sur des entiers, moyenne_a somme exactement puis divise une fois ; moyenne_c accumule des flottants et peut donc arrondir à chaque addition. Sur de grands tableaux, les deux résultats peuvent différer dans les derniers chiffres — chapitre 2.

Les autres exercices de ce chapitre Le cours du chapitre

Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.