25 - Spécification et tests
Les exercices se font dans Thonny, dans le fichier 25_specification_et_tests.py.
Cours associé : 18 - Spécification et tests .
Exercice 1 : QCM – vérification des prérequis
Pour chaque question, une seule réponse est correcte.
1. À quoi sert une docstring dans une fonction Python ?
- A. À accélérer l’exécution de la fonction
- B. À documenter ce que fait la fonction, ses paramètres et sa valeur de retour
- C. À empêcher la modification de la fonction
- D. À afficher automatiquement le résultat de la fonction
Correction publiée après la séance.
2. Que fait l’instruction assert condition, message en Python ?
- A. Elle affiche le message
- B. Si la condition est vraie, elle lève une erreur avec le message
- C. Si la condition est fausse, elle lève une erreur
AssertionErroravec le message - D. Elle commente le code pour la documentation
Correction publiée après la séance.
3. Qu’appelle-t-on une précondition ?
- A. Le résultat attendu de la fonction
- B. Une condition qui doit être vraie avant l’appel de la fonction pour qu’elle fonctionne correctement
- C. Une variable globale utilisée par la fonction
- D. La première ligne de code de la fonction
Correction publiée après la séance.
4. Quel est l’intérêt d’écrire des tests pour une fonction ?
- A. Accélérer l’exécution de la fonction
- B. Vérifier automatiquement que la fonction produit les résultats attendus sur des cas connus
- C. Empêcher les utilisateurs de modifier la fonction
- D. Supprimer les bugs sans avoir à lire le code
Correction publiée après la séance.
Exercice 2 : spécifier et tester avant de programmer
On veut une fonction est_premier(n) qui indique si un entier positif n est premier. On rappelle qu’un nombre premier a exactement deux diviseurs : 1 et lui-même.
Écrire la documentation de la fonction : ce qu’elle fait, son paramètre, sa précondition et ce qu’elle renvoie.
Indice
La documentation est une docstring, écrite entre
"""juste sous la lignedef est_premier(n):. Elle commence par une phrase qui dit ce que fait la fonction, puis donne trois rubriques :Paramètres :(le nom, le type et le sens den),Précondition :(ce que doit vérifiern) etRetour :(le type et le sens du résultat). La docstring de l’exercice 3.1 suit ce modèle.Avant d’écrire le code, prévoir le résultat de
est_premierpour 0, 1, 2, 7, 9, 97 et 100, puis écrire ces résultats sous forme de lignesassert.Indice
Un nombre premier a exactement deux diviseurs, 1 et lui-même : pour chaque nombre, chercher un autre diviseur en essayant 2, 3, 5… Attention à 0 et à 1 : combien ont-ils de diviseurs ? Chaque prévision s’écrit sous la forme
assert est_premier(7) == ....Compléter l’implémentation suivante :
def est_premier(n):
if n < 2:
return ...
for d in range(2, n):
if ...:
return False
return ...
Indice
n % d == 0 teste si d divise n. Si la boucle trouve un diviseur entre 2 et n - 1, n n’est pas premier ; si elle se termine sans en trouver, il l’est. Avant la boucle, les entiers inférieurs à 2 ne sont pas premiers.
Exécuter le programme avec les tests. Que signifie l’absence de message d’erreur ?
Indice
Une ligne
assertne dit rien quand sa condition est vraie. Se demander aussi ce que ces tests ne garantissent pas : on n’a essayé que sept entiers.
Correction publiée après la séance.
Exercice 3 : spécifications guidées
3.1 Compléter une docstring
Compléter la docstring de la fonction suivante :
def moyenne(L):
"""___
Paramètres :
L (___) : ___
Précondition :
___
Retour :
___ : ___
"""
total = 0
for x in L:
total += x
return total / len(L)
Indice
Lire le corps de la fonction : que contient L, et que calcule-t-elle ? Pour la précondition, chercher la liste pour laquelle le calcul total / len(L) est impossible.
Correction publiée après la séance.
3.2 Écrire des tests
Écrire au moins cinq assertions pour tester la fonction moyenne :
Indice
Chaque assertion compare un appel au résultat attendu, par exemple assert moyenne([2, 4]) == 3.0. Varier les cas : une liste d’un seul élément, des nombres négatifs, des valeurs toutes égales, des nombres à virgule. Avec des nombres à virgule, comparer avec une tolérance : abs(moyenne(...) - attendu) < 0.01.
Correction publiée après la séance.
Exercice 4 : préconditions et assert (vie courante)
1. Écrire une fonction indice_masse_corporelle(masse, taille) qui calcule l’IMC. Ajouter des assert pour vérifier les préconditions : la masse doit être strictement positive et la taille doit être strictement positive et inférieure à trois mètres.
Indice
L’IMC est la masse divisée par le carré de la taille : masse / taille ** 2. Chaque précondition devient une ligne assert condition, "message" placée en tête de la fonction ; 0 < taille < 3 s’écrit en une seule condition.
Code à copier puis compléter
def indice_masse_corporelle(masse, taille):
assert masse > 0, "La masse doit être strictement positive"
assert ..., "La taille doit être comprise entre 0 et 3 mètres"
return ...
2. Écrire une fonction note_sur_20(points, total) qui convertit un nombre de points en note sur 20. Ajouter des assert pour vérifier que points >= 0, total > 0 et points <= total.
Indice
La note sur 20 est la proportion de points obtenus, points / total, multipliée par 20. Les trois préconditions donnent trois lignes assert en tête de la fonction, comme à la question 1.
3. Tester chaque fonction avec des valeurs valides et des valeurs qui violent les préconditions (observer les erreurs).
Indice
Un appel qui viole une précondition arrête le programme sur une AssertionError : écrire ces appels un par un, et mettre chacun en commentaire avec # avant de passer au suivant.
Correction publiée après la séance.
Exercice 5 : jeux de tests – couverture des cas
Pour chaque fonction ci-dessous, proposer un jeu de tests couvrant les cas suivants : cas normal, cas limite, cas piégeux (valeurs négatives, doublons, égalités).
Indice
Un jeu de tests est une suite de lignes assert, par exemple assert est_pair(4) == True. Pour chaque fonction, chercher un cas ordinaire, un cas extrême (0, une liste vide, un élément en première ou en dernière position) et un cas piégeux.
1. est_pair(n) qui renvoie True si n est pair, False sinon.
Indice
Penser à 0 et aux nombres négatifs : sont-ils pairs ?
2. maximum(a, b, c) qui renvoie le plus grand des trois nombres.
Indice
Placer le plus grand nombre successivement en première, en deuxième et en troisième position, puis essayer des valeurs égales et des nombres négatifs.
3. chercher(L, x) qui renvoie l’indice de x dans L, ou -1 si x n’est pas dans L.
Indice
Essayer un élément au début, à la fin, un élément absent, une liste vide, et un élément présent deux fois : quel indice doit-on obtenir dans ce dernier cas ?
Correction publiée après la séance.
Exercice 6 : déboguer grâce aux tests (informatique)
Chacune des fonctions suivantes contient une erreur. Utiliser les tests fournis pour identifier et corriger le bug.
1.
def valeur_absolue(n):
"""Renvoie la valeur absolue de n."""
if n > 0:
return n
return n
# Tests :
assert valeur_absolue(5) == 5
assert valeur_absolue(0) == 0
assert valeur_absolue(-3) == 3 # Ce test échoue !
Indice
Suivre l’exécution pour n = -3 : quelle ligne est exécutée, et que renvoie-t-elle ? La valeur absolue de -3 est 3 : quelle opération transforme -3 en 3 ?
2.
def est_dans(L, x):
"""Renvoie True si x est dans L."""
for element in L:
if element == x:
return True
else:
return False
# Tests :
assert est_dans([1, 2, 3], 1) == True
assert est_dans([1, 2, 3], 3) == True # Ce test échoue !
assert est_dans([], 5) == False
Indice
Suivre l’exécution de est_dans([1, 2, 3], 3) : que se passe-t-il dès le premier élément ? Un return arrête la fonction, même au milieu d’une boucle.
3.
def compter(L, x):
"""Compte le nombre d'occurrences de x dans L."""
compteur = 0
for element in L:
if element == x:
compteur += 1
return compteur
# Tests :
assert compter([1, 2, 1, 3, 1], 1) == 3 # Ce test échoue !
assert compter([1, 2, 3], 4) == 0
Indice
Regarder l’indentation du return : à quel bloc appartient-il ? Combien de tours de boucle sont faits avant qu’il s’exécute ?
Correction publiée après la séance.
Exercice 7 : synthèse – spécifier, coder et tester un convertisseur
On souhaite écrire un convertisseur de températures complet.
1. Écrire la spécification (docstring avec paramètres, préconditions et retour) d’une fonction celsius_vers_fahrenheit(c).
Rappel : $F = \frac{9}{5} \times C + 32$
Indice
Reprendre le modèle de docstring de l’exercice 3.1 : une phrase sur le rôle de la fonction, puis Paramètres :, Précondition : et Retour :. La précondition vient de la physique : aucune température n’est inférieure au zéro absolu, −273,15 °C.
2. Écrire un jeu de tests avant l’implémentation, couvrant :
- le point de congélation de l’eau (0 °C → 32 °F) ;
- le point d’ébullition (100 °C → 212 °F) ;
- une température négative (−40 °C → −40 °F, cas remarquable où les deux échelles se croisent) ;
- le zéro absolu (−273,15 °C → −459,67 °F).
Indice
Écrire un assert par température de la liste, avec la valeur attendue en °F, par exemple assert celsius_vers_fahrenheit(100) == 212. Ces tests échouent tant que la fonction n’est pas écrite : c’est normal.
3. Implémenter la fonction avec une vérification de la précondition (la température ne peut pas être inférieure au zéro absolu).
Indice
Commencer la fonction par une ligne assert qui vérifie que c est supérieur ou égal à -273.15, puis renvoyer le résultat de la formule. Le calcul en nombres à virgule n’est pas exact : arrondir le résultat avec round(..., 2) pour que le test du zéro absolu passe.
4. Écrire la fonction inverse fahrenheit_vers_celsius(f) avec sa spécification, ses tests et sa vérification de précondition.
Indice
Isoler C dans la formule du rappel : on soustrait 32, puis on multiplie par 5 et on divise par 9. Le zéro absolu vaut −459,67 °F : c’est la nouvelle précondition. Les étapes sont celles des questions 1 à 3.
5. Vérifier la cohérence des deux fonctions : pour toute température $c$, on doit avoir fahrenheit_vers_celsius(celsius_vers_fahrenheit(c)) ≈ c (à une erreur d’arrondi près).
Indice
Choisir quelques températures, rangées dans une liste, et pour chacune vérifier avec assert que l’aller-retour redonne c à 0,01 près : abs(resultat - c) < 0.01.
Correction publiée après la séance.