Connexion élèves

Choisir le(s) module(s) à installer :

Tester un code et évaluer son efficacité.

Tests sur les entrées d'un code : les assertions

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 notamment 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.
	
	>>> 	
			

Tests du résultat d'une fonction : les tests unitaires

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...

Évaluer l'efficacité

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)
			

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 ).