TD I1 : Programmation pilotée par les TESTS¶
!!! info Objectifs de ce travail
Découverte du mot clé assert
et l'utiliser pour aborder le principe de la programmation pilotée par les tests. Il s'agit d'estimer si une fonction est correcte ou pas. Bien comprendre que l'on ne parle pas d'une démonstration, mais juste d'une bonne methode permettant de se "rassurer" sur le bon fonctionnement d'une fonction.
!!!
I. Utilisation raisonnée du mot clé "assert"¶
Q1. Écrire une fonction racine_carree(x : float) -> float
dont le but est de calculer la racine carrée du nombre reçu. On prendra soin d'utiliser le mot clé assert
pour s'assurer de la validité du nombre reçu.
# Fonction racine carrée
import math
def racine_carre(x):
assert x>=0
return math.sqrt(x)
racine_carre(-3)
Traceback (most recent call last): File "<basthon-input-1-bb90a7972116>", line 9, in <module> racine_carre(-3) File "<basthon-input-1-bb90a7972116>", line 6, in racine_carre assert x>=0 ^^^^ AssertionError
II. Programmation par les tests¶
Lors de la conception d'un pogramme, il est une bonne pratique de concevoir pour chaque fonction écrite, un jeu de tests qui permet d'augmenter les chances que celle-ci soit correcte.
Exercice 1 : un exemple trivial¶
Quelqu'un nous a fourni un module nommé mes_fonctions
qui contient une fonction double(x : float)-> float
qui doit renvoyer le double de la valeur reçue en paramètre. Exécuter les cellules ci-dessous pour obtenir des information via l'aide intégrée :
import mes_fonctions as mf
dir(mf)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'double']
help(mf.double)
Help on function double in module mes_fonctions: double(x) reçoit un nombre flottant x et renvoie son double
Pour tester la validité de cette fonction, jusqu'à présent on aurait utilisé basiquement la fonction print(...)
en faisant quelque chose comme cela:
print(mf.double(3))
print(mf.double(0))
print(mf.double(-1))
Mais l'inconvénient est que si par exemple la fonction donne de temps en temps des bonnes valeurs mais pas tout le temps, il faudra corriger le code de la fonction, puis effectuer à nouveau tous les tests. C'est un peu lourd. L'idée est donc de regrouper tous ces tests dans une fonction dédiée et d'utiliser le mot clé assert
plutôt que la fonction print:
!!! warning Principe "first test design"
Pour chaque fonction créee, on prendra l'habitude d'écrire en amont une (ou plusieurs) fonction test (dont le nom commence par test_
suivi du nom de la fonction). Cette syntaxe est usuelle, car utilisée par des logiciels tiers qui automatisent leur exécution (mais on ne s'en servira pas cette année...).
!!!
Q3. Faire quelques tests via la création d'une fonction test_double()
pour voir si la fonction double
semble correcte
# tests sur la fonction double
def test_double():
assert mf.double(2)==4
assert mf.double(-1) == -2
assert mf.double(0) == 0
print("test ok")
test_double()
Traceback (most recent call last): File "<basthon-input-6-155a7ae6dd1d>", line 9, in <module> test_double() File "<basthon-input-6-155a7ae6dd1d>", line 5, in test_double assert mf.double(-1) == -2 ^^^^^^^^^^^^^^^^^^^ AssertionError
On devrait constater que la fonction double
du module mes_fonctions
ne fonctionne pas toujours ! Présence du bandeau rouge en sortie, avec la mention AssertionError qui précise en quelle ligne notre test a échoué.
Il faudrait la corriger... (mais inutile ici, c'était juste pour l'exemple !)
Remarque : on aurait aussi pu faire beaucoup plus de tests en comparant notre fonction à une fonction que l'on sait être correcte (ici la simple multiplication par deux). Par exemple ainsi (mais on ne sait plus quelle valeur fait échouer le test...):
def test_double():
from random import randint
for i in range(100):
x = randint(-50,50)
assert double(x) == 2*x
test_double()
Exercice 2 : deux fonctions puissances¶
On souhaite écrire une fonction puissance(nombre : float, exposant : int)
qui calcule nombre
à la puissance exposant
. On se contentera d'exposants entiers et positifs ou nuls.
Q4. Commencer par écrire une fonction test_puissance()
qui propose quelques tests bien choisis pour voir si nos futures fonctions seront valides.
def test_puissance():
assert puissance(2,2) == 2**2
assert puissance(-2,2) == (-2)**2
assert puissance(0,2) == 0
assert puissance(2,0) == 1
assert puissance(2.5,2) == 2.5**2
print("test ok")
Q5. Écrire ensuite une fonction puissance(nombre : float, exposant : int)
qui calcule nombre
à la puissance exposant
. Attention, on demande d'utiliser assert
pour n'accepter que des entiers positifs ou nuls pour exposant
, et de ne pas utiliser la commande **
pour calculer la puissance...
On appelle bien sûr la fonction test_puissance()
pour passer les tests !
def puissance(nombre,exposant):
assert exposant >= 0
resultat = 1
for i in range(exposant):
resultat = resultat * nombre
return resultat
test_puissance()
test ok
Q6. On considère la fonction mystère ci-dessous. Appliquez-lui les même tests que précédemment pour constater qu'elle fait la même chose.
def mystere(x,n):
if n == 0 :
return 1
if n%2 == 0 :
m = mystere(x,n//2)
return m*m
else :
m = mystere(x,n-1)
return x*m
def test_mystere():
# à compléter par les mêmes tests que dans test_puissance....
assert mystere(2,2) == 2**2
assert mystere(-2,2) == (-2)**2
assert mystere(0,2) == 0
assert mystere(2,0) == 1
assert mystere(2.5,2) == 2.5**2
print("Tests OK")
test_mystere()
Tests OK
Q7. Donner ci-dessous une version améliorée de votre fonction test
pour qu'elle teste la fonction puissance(n,e)
sur tous les nombres entiers $n$ entre -10 et 10 à toutes les puissances $e$ entre 0 et 100 (besoin de boucles imbriquées...) en comparant les résultats à ceux obtenus avec l'opérateur python **
.
def test_puissance():
# à compléter...
for n in range(-10,11):
for e in range(0,101):
assert puissance(n,e) == n**e
print("Tous les tests sont passés")
test_puissance()
Tous les tests sont passés
Exercie 3 : « refactoring » du tri par sélection¶
Il y a beaucoup de façon différentes pour trier une liste. Voyons ici un algorithme intuitif, le tri par sélection. Le principe est de parcourir plusieurs fois la liste, repérer à chaque fois le plus petit et faire une permutation. Voir cette vidéo youtube. On donne ci-dessous un algorithme brut qui implémente cette façon de trier. Le lire attentivement et bien faire le parallèle avec la vidéo.
def selection(tab):
tab1 = tab.copy()
for i in range(len(tab1) - 1):
min_index = i
for j in range(i + 1, len(tab1)):
if tab1[j] < tab1[min_index]:
min_index = j
tab1[i], tab1[min_index] = tab1[min_index], tab1[i]
return tab1
L'objectif de cet exercice est de refactoriser cette fonction, c'est à dire la découper en fonctions élémentaires afin d'améliorer la lisibilité du code. On identifie en effet deux tâches élémentaires :
- repérer l'indice du plus petit élément dans une liste, à partir d'un certain indice,
- permuter deux élements de la liste dont on connait les indices.
Q9. Écrire une fonction test_selection()
qui permet de tester sur quelques exemples si la fonction selection
semble correcte.
def test_selection():
assert selection([]) == []
assert selection([2]) == [2]
assert selection([1,2,3]) == [1,2,3]
assert selection([3,2,1]) == [1,2,3]
print("Tests OK")
test_selection()
Tests OK
Maintenant que l'on dispose d'une fonction de test, on peut se lancer dans la modification de la fonction selection
et tester à chaque modification...
Q10. Écrire une fonction permuter(tab,i,j)
qui renvoie une copie de la liste tab
dont les éléments d'indice i
et j
ont été permuté. Bien entendu, on doit aussi écrire une fonction de test associée.
def test_permuter():
assert permuter([1,2,3],1,2) == [1,3,2]
assert permuter([1,2,3],2,1) == [1,3,2]
print("Tests OK")
def permuter(tab : list, i : int, j : int) :
"""
on reçoit une liste et deux indices i et j. La fonction
renvoie une copie de la liste dans laquelle on a permuté
les valeurs des indices i et j"""
tab1 = tab.copy()
a,b = tab[i], tab[j]
tab1[i] = b
tab1[j] = a
return tab1
test_permuter()
Tests OK
Q11. Écrire une fonction min_indice_tableau(tab,i)
qui doit renvoyer l'indice du plus petit élément du tableau tab
à partir de l'indice i
. Penser à la focntion test associée.
def test_min_indice_tableau():
assert min_indice_tableau([1,2,3],0) == 0
assert min_indice_tableau([1,2,3],1) == 1
assert min_indice_tableau([3,2,1],0) == 2
assert min_indice_tableau([3,2,1],1) == 2
print("Tests OK")
def min_indice_tableau(tab,i):
"""
renvoie l'indice du plus petit élément du tableau tab
compté à partir de l'indice i
"""
v_mini = tab[i]
i_mini = i
for j in range(i+1, len(tab)):
if tab[j] < v_mini :
v_mini = tab[j]
i_mini = j
return i_mini
test_min_indice_tableau()
Tests OK
Q12. Réécrire enfin la fonction selection(tab)
en utilisant les deux fonctions permuter
et min_indice_tableau
.
def selection(tab):
""" Renvoie une copie du tableau trié par la méthode
du tri par sélection"""
tab1 = tab.copy()
for i in range(len(tab1)):
min_index = min_indice_tableau(tab1,i)
tab1 = permuter(tab1,i,min_index)
return tab1
test_selection()
Tests OK