Vous en avez assez appris pour déconstruire les sept premières
lignes du code d'exemple de ce chapitre : lire un répertoire et
importer des modules sélectionnés parmi ceux qu'il contient.
Regardons cela ligne par ligne. Supposons que le répertoire en
cours est c:\diveintopython\py, qui contient les
exemples du livre, y compris le script de ce chapitre. Comme vous l'avez
vu à la Section 16.2, «Trouver le chemin», le répertoire du script est
assigné à la variable path, commençons donc à cette
étape.
Exemple 16.17. Etape 1 : Obtenir la liste des fichiers
files est une liste de tous les fichiers
et les répertoires du répertoire du script (si vous avez déjà
exécuté certains exemples, vous verrez également des fichiers
.pyc).
Cette expression régulière reconnaît toutes les chaînes qui
finissent par test.py. Notez que nous devons
utiliser le caractère d'échappement pour le point, un point dans
une expression régulière signifiant «n'importe quel
caractère», ce que nous voulons c'est bien un point.
L'expression régulière compilée agit comme une fonction,
nous pouvons donc l'utiliser pour filtrer la liste de fichiers et
de répertoires.
Ce qu'il reste est la liste des scripts de tests unitaires
puisque ce sont les seuls nommés
QUELQUECHOSEtest.py.
Exemple 16.19. Etape 3 : Mutation des noms de fichiers en noms de
modules
Comme vous l'avez vu à la Section 4.7, «Utiliser des fonctions lambda», lambda est une
manière rapide de créer des fonctions incluses d'une ligne.
Celle-ci prend un nom de fichier avec une extension et le retourne
sans son extension en utilisant la fonction de la bibliothèque
standard os.path.splitext que vous avez vu à
l'Exemple 6.17, «Division de noms de chemins».
filenameToModuleName est une fonction. Il
n'y a rien qui différencie les fonctions lambda
des fonctions habituelles définies par l'instruction
def. Nous pouvons appeler la fonction
filenameToModuleName comme n'importe quelle
autre et elle fait exactement ce que nous voulons qu'elle fasse :
enlever l'extension du nom de fichier passé en argument.
Maintenant nous pouvons appliquer cette fonction à chaque
nom de fichier de la liste de fichier de tests unitaires à l'aide
de map.
Le résultat est bien ce que nous souhaitons : une liste de
modules sous forme de chaînes.
Exemple 16.20. Etape 4 : Mutation des noms de modules en modules
>>> modules = map(__import__, moduleNames) >>> modules[<module 'apihelpertest' from 'apihelpertest.py'>,
<module 'kgptest' from 'kgptest.py'>,
<module 'odbchelpertest' from 'odbchelpertest.py'>,
<module 'pluraltest' from 'pluraltest.py'>,
<module 'romantest' from 'romantest.py'>]>>> modules[-1]<module 'romantest' from 'romantest.py'>
Comme vous l'avez vu à la Section 16.6, «Importation dynamique de modules», nous pouvons utiliser
map et __import__ pour
transformer une liste de noms de modules (sous forme de chaînes)
en une liste de modules (que nous pouvons appeler comme n'importe
quel autre module).
modules est maintenant une liste de
modules, totalement accessibles comme tout autre module.
Le dernier module de la liste est le
module romantest, comme si
nous avions écrit import romantest.
Exemple 16.21. Etape 5 : Chargement des modules en une suite de tests
Ce sont de véritable objets-modules. Nous pouvons non
seulement y accéder comme à tout autre module, instancier des
classes et appeler des fonctions, nous pouvons également utiliser
l'instrospection pour déterminer quelles fonctions et classes il
contient. C'est ce que la méthode
loadTestsFromModule fait : elle utilise
l'instrospection et retourne un objet
unittest.TestSuite pour chaque module. Chaque
objet TestSuite contient en fait une liste
d'objets TestSuite, un pour chaque classe
TestCase du module et chacun de ces objets
TestSuite contient une liste de tests, un pour
chaque méthode de test du module.
Finalement, nous regroupons la liste d'objets
TestSuite en une seule suite de tests. Le
module unittest n'a aucun
mal à parcourir cet arbre de suites de tests imbriquées, il
recherche une méthode de test, l'exécute, vérifie que le test
passe ou échoue et continue de parcourir l'arbre jusqu'à la
prochaine méthode de test.
Ce processus d'introspection est ce que le module unittest fait d'habitude pour nous. Vous
vous rappelez de cette fonction magique
unittest.main() que nos modules de test appelaient
pour démarrer le processus ? unittest.main() crée
en fait une instance de unittest.TestProgram, qui
crée à son tour une instance de
unittest.defaultTestLoader et le charge avec le
module appelant (comment obtient-il une référence au module appelant
sans qu'on lui en donne une ? En utilisant une instruction tout aussi
magique, __import__('__main__'), qui importe
dynamiquement le module en cours d'exécution. Je pourrais écrire un
livre sur tous les trucs et les techniques utilisé dans le module
unittest, mais dans ce cas je ne
finirais jamais celui-ci).
Exemple 16.22. Etape 6 : Passage de la suite de tests à unittest
if __name__ == "__main__":
unittest.main(defaultTest="regressionTest")
Au lieu de laisser le module unittest opérer sa magie pour nous,
nous avons fait la majeure partie du travail nous-même. Nous avons
créé une fonction (regressionTest) qui importe
les modules, appelé unittest.defaultTestLoader
et regroupé l'ensemble en une suite de tests. Maintenant, tout ce
dont nous avons besoins est de dire à unittest qu'il doit, au lieu de
rechercher des tests et de construire une suite de tests de la
manière habituelle, appeler simplement la fonction
regressionTest, qui retourne une
TestSuite prête à l'emploi.