Version 2, last updated by abergeron at April 19, 2010 09:05 UTC

J'ai ajouté un script simple pour tester avec doctest les modules que l'on développe.

Exemple simple

Les exemples sont parlants alors en voici un:

def print_hello(times):
r"""
Prints hello `times` times.

Tests:
>>> print_hello(1)
hello
>>> print_hello(3)
hello
hello
hello
"""
for i in range(times):
print "hello"

Dans cet example la portion entre r""" et """ est appelée le docstring.  Le script scanne tous les docstring dans le code pour les lignes qui commence par >>> (en excluant des espaces blancs au début).  Toutes ces lignes sont exécutées et la sortie est comparée au résultat attendu écrit en dessous.  Si c'est pareil, tout va bien sinon, une erreur est reportée.

Les lignes d'un même bloc de tests sont exécutées une après l'autre en conservant les variables définies et autres facteurs de l'environnement.  Il est donc possible de faire des test qui comptent sur ceux qui précèdent.

r"""
>>> a = 5
>>> b = 3
>>> a + b
8
"""

Par contre, l'environnement est mis à zéro entre les blocs donc il ne faut pas compter sur l'effet d'un autre test dans ses tests.

L'environnement accessible dans les tests comprend toutes les fonctions et classes définies dans le fichier contenant les tests ainsi que tout ce qui est directement accessible à partir de ift6266 (rien au moment de l'écriture).  Si vous avez besoin d'autres modules il faut les importer avant des les utiliser.

Traitement des exceptions

Si veut tester qu'une fonction lance une exception dans un certain contexte on le fait ainsi

def test_ex():
r"""
>>> test_ex()
Traceback (most recent call last):
...
Exception:
"""
raise Exception

L'ellipse (...) signifie que l'on n'est pas intéressé par le détail de la provenace de l'exception.

Sortie aléatoires

Si on a une fonction dont une portion de sa sortie est aléatoire on peut tester le reste de la sortie en masquant cette portion

import random

def print_rand(max):
r"""
>>> print_rand(6)
Nombre choisi: ...
"""
print "Nombre choisi:", random.randint(0, max)

Encore une fois la présence d'une ellipse (...) indique que l'on n'est pas intéressé pas les détails.  Ce qui est autour doit correspondre par contre.

Résultats

Lorsque qu'aucune erreur n'est détectée le script affiche la liste des modules testés

[bergearn@maggie46 ift6266]$ python test.py
Testing: ift6266.datasets.dataset
Testing: ift6266.datasets.dsetiter
Testing: ift6266.datasets.ftfile
Testing: ift6266.datasets.nist
[bergearn@maggie46 ift6266]$

Si un des tests signale une erreur la sortie devient

[bergearn@maggie46 ift6266]$ python test.py
Testing: ift6266.datasets.dataset
Testing: ift6266.datasets.dsetiter
**********************************************************************
File "/u/bergearn/local/lib/python2.5/site-packages/ift6266/datasets/dsetiter.py", line 27, in ift6266.datasets.dsetiter.DataIterator.__init__
Failed example:
    d.batchsize
Expected:
    11
Got:
    10
**********************************************************************
1 items had failures:
   1 of  24 in ift6266.datasets.dsetiter.DataIterator.__init__
***Test Failed*** 1 failures.
Testing: ift6266.datasets.ftfile
Testing: ift6266.datasets.nist
[bergearn@maggie46 ift6266]$

Dans cet exemple il y avait un test exécutant

>>> d.batchsize

qui s'attendait à recevoir 11 mais a reçu 10.  On a en haut le fichier contenant le test qui n'a pas passé et la ligne du test.  (Cet exemple est tiré de mon code pour les datasets)

Autres détails (*important*)

Le script traverse récursivment tous les sous-modules de ift6266 (tous les répertoires contenant un fichier __init__.py) à l'exception de scripts (qui est inconditonnelement ignoré parce que la plupart des fichiers contenus s'exécutent lorsqu'il sont importés et même certains lancent des jobs sur le cluster)

Faites attention à ce que les fichiers python soit protégés contre l'importation pour que le script de test ne lance pas de travail.  Pour se protéger, on met le code que l'on veut exécuter lorsqu'on tape python <fichier>.py dans une section

if __name__ == '__main__':
<code à exécuter ici>