Lorsqu'on écrit un programme, on ne peut jamais prévoir toutes les situations possibles qui se produiront lorsque ce programme s’exécutera, et particulièrement toutes les valeurs possibles que pourra entrer par exemple l'utilisateur du programme et celles des arguments passés à une fonction.
Or, un code est prévu pour fonctionner normalement avec des paramètres d'un certain type, voire d'une certaine valeur bien précise !
Il est alors possible, dans l'écriture d'un code, de directement spécifier quelles préconditions doivent être respectées pour une bonne exécution du code, ce sont les assertions ( de manière générale, une assertion est une chose qui doit être considérée comme vraie. ).
Si ces préconditions ne sont pas vérifiées, alors le programme s'arrête ( il lève une exception ); au programmeur de faire alors en sorte que les préconditions soient toujours vérifiées, en rajoutant par exemple des tests sur les valeurs des paramètres avant l’exécution du code.
Prenons l'exemple d'une fonction qui calcule l'inverse d'un nombre entier positif :
def inverse(n):
'''
Calcule l'inverse d'un entier positif
Entrée : un entier n entier strictement positif
Sortie : un flottant, inverse de n
'''
return 1/n
Un utilisateur "lambda" ne consultera pas forcément la docstring de la fonction ( spoiler : il ne le fera jamais... ), et il sera libre d'entrer n'importe quelle valeur, qui ne respectera donc pas forcément les préconditions ( n entier strictement positif ).
On peut alors rajouter des assertions avant l’exécution du code de la fonction :
def inverse(n):
'''
Calcule l'inverse d'un nombre positif
Entrée : un entier n strictement positif
Sortie : un flottant, inverse de n
'''
assert n != 0 , "Le nombre ne doit pas être nul."
assert n > 0 , "Le nombre doit être positif."
assert isinstance(n, int) , "Le nombre doit être un entier."
return 1/n
Ces assertions lèveront une exception et afficheront un message si une des préconditions n'est pas respectée :
>>> inverse(12)
0.08333333333333333
>>> inverse(0)
Traceback (most recent call last):
File "", line 1, in
File "test.py", line 9, in inverse
assert n != 0 , "Le nombre ne doit pas être nul."
AssertionError: Le nombre ne doit pas être nul.
>>> inverse(-2)
Traceback (most recent call last):
File "", line 1, in
File "test.py", line 10, in inverse
assert n > 0 , "Le nombre doit être positif."
AssertionError: Le nombre doit être positif.
>>> inverse(1.2)
Traceback (most recent call last):
File "", line 1, in
File "test.py", line 11, in inverse
assert isinstance(n, int) , "Le nombre doit être un entier."
AssertionError: Le nombre doit être un entier.
>>>
assert condition :
de nombreuses possibilités existent pour la condition :
assert ==, <, >, != ...
assert isinstance (n, int)
assert n in [1,2,3]
def interface():
req = str(input("voulez vous saisir un titre de film, un acteur ou un réalisateur ? "))
if req == "titre":
print('appel de la fonction effectuant une requete sur un titre de film')
elif req == "acteur":
print('appel de la fonction effectuant une requete sur un acteur')
elif req == "réalisateur":
print('appel de la fonction effectuant une requete sur un réalisateur')
def division_euclidienne(a, b):
q=a//b
r=a%b
return q,r
def dec_vers_bin(n,bits):
'''convertit le nombre n en binaire sur un nombre de bits'''
binaire=""
for a in range(bits):
binaire=str(n%2)+binaire
n=n//2
return binaire
somme()
ci-dessous :
def somme(a,b):
return a+b
On peut imaginer la fonction de test :
def test_somme():
assert somme(0,0)==0
assert somme(1,1)==2
assert somme(1,-1)==0
assert somme(12,3)==15
assert somme(12,-3)==9
assert somme(50,100)==150
assert
. La notion de tests est fondamentale en informatique : certains systèmes ne peuvent se contenter d'un fonctionnement approximatif mais doivent au contraire être robustes, c'est à dire fonctionner correctement dans toutes les situations possibles.
Pour prouver qu'une fonction fait toujours correctement le travail pour lequel elle est prévue, il faudrait théoriquement la tester avec tous les arguments possibles et imaginables; c'est bien entendu impossible...
On peut cependant se contenter de tester son bon fonctionnement sur quelques arguments bien choisis : on parle alors de tests unitaires
En Python, on peut utiliser le module doctest
pour réaliser ces tests unitaires; il permet d'indiquer dans la docstring de la fonction des tests à réaliser et le résultat attendu si elle fonctionne bien.
Si ce n'est pas le cas, un message est alors affiché signalant qu'il faut corriger son code !
Voici un exemple avec une fonction qui donne le quotient et le reste de deux entiers :
import doctest
def division( n1, n2 ):
"""
Fonction pour calculer le quotient et le reste de la division de deux nombres.
Entrée :
deux nombres n1, n2
Sortie :
deux entiers dans l'ordre : quotient, reste de la division de n1 par n2
Tests unitaires :
>>> division(1,1)
(1, 0)
>>> division(2,1)
(2, 0)
>>> division(10,3)
(3, 1)
"""
quotient = n1 // n2
reste = n1 % n2
return quotient, reste
# Programme principal
doctest.testmod() # exécution des test unitaires
Dans la docstring, un test unitaire correspond aux 3 chevrons (>>>
) suivis de l'appel de la fonction avec des arguments particuliers; on indique en dessous le résultat attendu.
Si les tests unitaires sont validés, rien ne s'affiche. Par contre, en cas de mauvais fonctionnement, un ou plusieurs avertissement(s) s'affichent indiquant quel(s) test(s) n'ont pas été réussi(s).
Exemple avec la fonction précédente buguée :
import doctest
def division( n1, n2 ):
"""
Fonction pour calculer le quotient et le reste de la division de deux nombres.
Pré :
deux nombres n1, n2
Post :
deux entiers dans l'ordre : quotient, reste de la division de n1 par n2
Tests unitaires :
>>> division(1,1)
(1, 0)
>>> division(2,1)
(2, 0)
>>> division(10,3)
(3, 1)
"""
quotient = n1 / n2 # LA FONCTION EST BUGUÉE !!!
reste = n1 % n2
return quotient, reste
# Programme principal
doctest.testmod() # exécution des test unitaires
*********************************************************************
File "test.py", line 14, in __main__.division
Failed example:
division(1,1)
Expected:
(1, 0)
Got:
(1.0, 0)
**********************************************************************
File "test.py", line 17, in __main__.division
Failed example:
division(2,1)
Expected:
(2, 0)
Got:
(2.0, 0)
**********************************************************************
File "test.py", line 20, in __main__.division
Failed example:
division(10,3)
Expected:
(3, 1)
Got:
(3.3333333333333335, 1)
**********************************************************************
1 items had failures:
3 of 3 in __main__.division
***Test Failed*** 3 failures.
>>>
La fonction renvoyant un flottant alors que c'est un entier qui est attendu, une erreur est signalée.
Une méthode de développement appelé TDD préconise d'ailleurs d'écrire D'ABORD des tests avant même le code d'une fonction...
def somme (N):
somme = 0
for i in range(1, N+1):
somme = somme + i
return somme
def dec_vers_bin(n,bits):
'''convertit le nombre n en binaire sur un nombre de bits'''
binaire=""
for a in range(bits):
binaire=str(n%2)+binaire
n=n//2
return binaire
L=['toto',2,1.23,True
a=1
Cependant, la console python nous indiquera dans ce cas le message suivant :
File "test.py", line 2
a=1
^
SyntaxError: invalid syntax
Il semble difficile de déceler une erreur à la ligne indiquée par la console python (pas d'erreur de syntaxe dans a=1
)L
et bute sur le signe =
.
1. IndexError: list index out of range
2. SyntaxError: invalid syntax
3. TypeError: 'int' object is not subscriptable
4. NameError: name 'variable' is not defined
5. SyntaxError: EOL while scanning string literal
6. TypeError: unsupported operand type(s) for /: 'str' and 'str'
7. ValueError: invalid literal for int() with base 10: '12.5'
8. TypeError: object of type 'float' has no len()
9. IndentationError: unexpected indent
10. IndentationError: expected an indented block
11. IndentationError: unindent does not match any outer indentation level
12. TypeError: f1() takes 0 positional arguments but 1 was given
13. TypeError: f2() takes 1 positional argument but 2 were given
14. ValueError: invalid literal for int() with base 10: '12.5'
print
en précisant à chaque fois quelle variable est affichée (print('i=',i)
plutôt que print(i) et en commentant cet affichage de test(#test
)
from random import randint
nombre = randint( 1 , 100
entree = 0
while ( entree == nombre ) :
entree = int(input('Entrez votre proposition :'))
if ( entree > nombre )) :
print('Trop grand !')
elif ( entree < nombre )
print('Trop petit !')
print('Bravo !')
Si on veut par exemple comparer l'efficacité de deux programmes différents, on peut vouloir mesurer leur temps d'exécution.
Un module existe pour cela, le module timeit
. Par exemple, pour mesurer la durée d’exécution d'une fonction :
from timeit import timeit
def test(n):
"""Fonction inutile"""
L = [i for i in range(n)]
t = timeit(stmt = 'test(1000)', globals=globals(), number = 100)
print(t)
test()
avec un paramètre n égal à 1000 )Le module mesure la durée moyenne ( en secondes ) de l’exécution de la fonction test()
sur plusieurs exécutions successives ( cette durée est souvent influencée par de nombreux paramètres extérieurs au code ).