J'ai un répertoire qui contient mes tests unitaires Python. Chaque module de test unitaire est de la forme test _ *. Py. J'essaie de créer un fichier appelé all_test.py qui, vous l'aurez deviné, exécutera tous les fichiers dans le formulaire de test susmentionné et renverra le résultat. J'ai essayé deux méthodes jusqu'à présent. les deux ont échoué. Je vais montrer les deux méthodes et j'espère que quelqu'un sait comment le faire correctement.
Pour ma première tentative courageuse, je me suis dit "Si je viens d'importer tous mes modules de test dans le fichier, puis d'appeler cette unittest.main()
doodad, cela fonctionnera, n'est-ce pas?" Eh bien, je me suis trompé.
import glob
import unittest
testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
if __== "__main__":
unittest.main()
Cela n'a pas fonctionné, le résultat obtenu est le suivant:
$ python all_test.py
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
Pour mon deuxième essai, j’ai pensé, d’accord, peut-être que je vais essayer de faire tout ce processus d’essai de manière plus "manuelle". J'ai donc essayé de le faire ci-dessous:
import glob
import unittest
testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
[__import__(str) for str in module_strings]
suites = [unittest.TestLoader().loadTestsFromName(str) for str in module_strings]
[testSuite.addTest(suite) for suite in suites]
print testSuite
result = unittest.TestResult()
testSuite.run(result)
print result
#Ok, at this point I have a result
#How do I display it as the normal unit test command line output?
if __== "__main__":
unittest.main()
Cela aussi n'a pas fonctionné, mais cela semble si proche!
$ python all_test.py
<unittest.TestSuite tests=[<unittest.TestSuite tests=[<unittest.TestSuite tests=[<test_main.TestMain testMethod=test_respondes_to_get>]>]>]>
<unittest.TestResult run=1 errors=0 failures=0>
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
Je semble avoir une suite quelconque et je peux exécuter le résultat. Je suis un peu préoccupé par le fait qu'il est dit que je n'ai que run=1
, il semble que cela devrait être run=2
, mais c'est un progrès. Mais comment puis-je passer et afficher le résultat au principal? Ou comment puis-je le faire fonctionner pour que je puisse simplement exécuter ce fichier et, ce faisant, exécuter tous les tests unitaires de ce répertoire?
Vous pouvez utiliser un testeur qui le ferait pour vous. nez est très bon par exemple. Une fois exécuté, il trouvera des tests dans l’arborescence actuelle et les exécutera.
Mis à jour:
Voici un code de mes jours d'avant le nez. Vous ne voulez probablement pas la liste explicite des noms de modules, mais peut-être que le reste vous sera utile.
testmodules = [
'cogapp.test_makefiles',
'cogapp.test_whiteutils',
'cogapp.test_cogapp',
]
suite = unittest.TestSuite()
for t in testmodules:
try:
# If the module defines a suite() function, call it to get the suite.
mod = __import__(t, globals(), locals(), ['suite'])
suitefn = getattr(mod, 'suite')
suite.addTest(suitefn())
except (ImportError, AttributeError):
# else, just load all the test cases from the module.
suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))
unittest.TextTestRunner().run(suite)
Avec Python 2.7 et versions ultérieures, vous n'avez pas besoin d'écrire de nouveau code ni d'utiliser des outils tiers pour le faire. l'exécution récursive du test via la ligne de commande est intégrée.
python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'
Vous pouvez en lire plus dans la documentation python 2.7 ou python 3.x unittest.
Ceci est maintenant possible directement à partir de unittest: nittest.TestLoader.discover .
import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)
runner = unittest.TextTestRunner()
runner.run(suite)
Dans python 3, si vous utilisez _unittest.TestCase
_:
__init__.py
_ dans votre répertoire test
(doit être nommé _test/
_)test/
_ correspondent au modèle _test_*.py
_. Ils peuvent se trouver dans un sous-répertoire sous _test/
_, et ces sous-répertoires peuvent être nommés de manière quelconque.Ensuite, vous pouvez exécuter tous les tests avec:
_python -m unittest
_
Terminé! Une solution moins de 100 lignes. Espérons qu'un autre _ débutant python gagne du temps en trouvant ceci.
En étudiant un peu le code ci-dessus (en utilisant spécifiquement TextTestRunner
et defaultTestLoader
), j'ai pu me rapprocher de plus près. Finalement, j'ai corrigé mon code en passant simplement toutes les suites de tests à un seul constructeur de suites, plutôt que de les ajouter "manuellement", ce qui corrigeait mes autres problèmes. Alors voici ma solution.
import glob
import unittest
test_files = glob.glob('test_*.py')
module_strings = [test_file[0:len(test_file)-3] for test_file in test_files]
suites = [unittest.defaultTestLoader.loadTestsFromName(test_file) for test_file in module_strings]
test_suite = unittest.TestSuite(suites)
test_runner = unittest.TextTestRunner().run(test_suite)
Oui, il est probablement plus facile d’utiliser le nez que de le faire, mais c’est tout.
Si vous souhaitez exécuter tous les tests de différentes classes de cas de test et que vous souhaitez les spécifier explicitement, vous pouvez le faire comme suit:
from unittest import TestLoader, TextTestRunner, TestSuite
from uclid.test.test_symbols import TestSymbols
from uclid.test.test_patterns import TestPatterns
if __== "__main__":
loader = TestLoader()
tests = [
loader.loadTestsFromTestCase(test)
for test in (TestSymbols, TestPatterns)
]
suite = TestSuite(tests)
runner = TextTestRunner(verbosity=2)
runner.run(suite)
où uclid
est mon projet et TestSymbols
et TestPatterns
sont des sous-classes de TestCase
.
J'ai utilisé la méthode discover
et une surcharge de load_tests
pour obtenir ce résultat en un nombre (minimal, je pense) de lignes de code:
def load_tests(loader, tests, pattern):
''' Discover and load all unit tests in all files named ``*_test.py`` in ``./src/``
'''
suite = TestSuite()
for all_test_suite in unittest.defaultTestLoader.discover('src', pattern='*_tests.py'):
for test_suite in all_test_suite:
suite.addTests(test_suite)
return suite
if __== '__main__':
unittest.main()
Exécution sur cinq ans quelque chose comme
Ran 27 tests in 0.187s
OK
J'ai essayé différentes approches mais toutes semblent imparfaites ou je dois maquiller du code, c'est embêtant. Mais il existe un moyen pratique sous Linux, qui consiste simplement à trouver tous les tests selon un certain modèle, puis à les invoquer un par un.
find . -name 'Test*py' -exec python '{}' \;
et surtout, ça marche vraiment.
Dans le cas d'une bibliothèque ou d'une application empaquetée , vous ne souhaitez pas le faire. setuptools
le fera pour vous .
Pour utiliser cette commande, les tests de votre projet doivent être encapsulés dans une suite de tests
unittest
par une fonction, une classe ou une méthode TestCase ou un module ou package contenant des classesTestCase
. Si la suite nommée est un module et que le module possède une fonctionadditional_tests()
, il est appelé et le résultat (qui doit être ununittest.TestSuite
) est ajouté aux tests à exécuter. Si la suite nommée est un package, des sous-modules et sous-packages sont ajoutés de manière récursive à la suite de tests globale .
Dites-lui simplement où se trouve votre paquet de test racine, par exemple:
setup(
# ...
test_suite = 'somepkg.test'
)
Et lancez python setup.py test
.
La découverte basée sur les fichiers peut être problématique dans Python 3, sauf si vous évitez les importations relatives dans votre suite de tests, car discover
utilise l'importation de fichier. Même s'il supporte l'option optionnelle top_level_dir
, mais j'ai eu des erreurs de récursion infinies. Ainsi, une solution simple pour un code non empaqueté consiste à mettre les éléments suivants dans __init__.py
de votre package de test (voir protocole load_tests ).
import unittest
from . import foo, bar
def load_tests(loader, tests, pattern):
suite = unittest.TestSuite()
suite.addTests(loader.loadTestsFromModule(foo))
suite.addTests(loader.loadTestsFromModule(bar))
return suite
J'utilise PyDev/LiClipse et je n'ai pas vraiment compris comment exécuter tous les tests en même temps à partir de l'interface graphique. (edit: vous faites un clic droit sur le dossier de test racine et choisissez Run as -> Python unit-test
Voici ma solution de contournement actuelle:
import unittest
def load_tests(loader, tests, pattern):
return loader.discover('.')
if __== '__main__':
unittest.main()
Je mets ce code dans un module appelé all
dans mon répertoire de test. Si je lance ce module comme unittest de LiClipse, tous les tests sont exécutés. Si je demande à ne répéter que des tests spécifiques ou ayant échoué, seuls ces tests sont exécutés. Cela n'interfère pas non plus avec mon testeur de ligne de commande (nosetest) - il est ignoré.
Vous devrez peut-être modifier les arguments en discover
en fonction de la configuration de votre projet.
Basé sur la réponse de Stephen Cagle , j'ai ajouté le support pour les modules de test imbriqués.
import fnmatch
import os
import unittest
def all_test_modules(root_dir, pattern):
test_file_names = all_files_in(root_dir, pattern)
return [path_to_module(str) for str in test_file_names]
def all_files_in(root_dir, pattern):
matches = []
for root, dirnames, filenames in os.walk(root_dir):
for filename in fnmatch.filter(filenames, pattern):
matches.append(os.path.join(root, filename))
return matches
def path_to_module(py_file):
return strip_leading_dots( \
replace_slash_by_dot( \
strip_extension(py_file)))
def strip_extension(py_file):
return py_file[0:len(py_file) - len('.py')]
def replace_slash_by_dot(str):
return str.replace('\\', '.').replace('/', '.')
def strip_leading_dots(str):
while str.startswith('.'):
str = str[1:len(str)]
return str
module_names = all_test_modules('.', '*Tests.py')
suites = [unittest.defaultTestLoader.loadTestsFromName(mname) for mname
in module_names]
testSuite = unittest.TestSuite(suites)
runner = unittest.TextTestRunner(verbosity=1)
runner.run(testSuite)
Le code recherche dans tous les sous-répertoires de .
les fichiers *Tests.py
qui sont ensuite chargés. Il s'attend à ce que chaque *Tests.py
contienne une seule classe *Tests(unittest.TestCase)
qui est chargée à son tour et exécutée l'une après l'autre.
Cela fonctionne avec une imbrication profonde arbitraire de répertoires/modules, mais chaque répertoire doit contenir au moins un fichier __init__.py
vide. Cela permet au test de charger les modules imbriqués en remplaçant les barres obliques (ou barres obliques inverses) par des points (voir replace_slash_by_dot
).
Ce script BASH exécute le répertoire de test python unittest de ANYWHERE dans le système de fichiers, quel que soit le répertoire de travail dans lequel vous vous trouvez: son répertoire de travail se trouve toujours à l'emplacement de ce répertoire test
.
TOUS TESTS, $ PWD indépendant
le module unittest Python est sensible à votre répertoire actuel, sauf si vous lui indiquez où (avec l'option discover -s
).
Ceci est utile lorsque vous restez dans le répertoire de travail ./src
ou ./example
et que vous avez besoin d'un test unitaire global rapide:
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"
python -m unittest discover -s "$readlink"/test -v
ESSAIS SÉLECTIONNÉS, $ PWD indépendants
Je nomme ce fichier d’utilitaire: runone.py
et l’utilise comme ceci:
runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"
(cd "$dirname"/test; python -m unittest $1)
Nul besoin d'un fichier test/__init__.py
pour surcharger votre paquet/mémoire supplémentaire pendant la production.
Comme la découverte de test semble être un sujet complet, il existe un cadre dédié à la découverte de test:
Plus de lecture ici: https://wiki.python.org/moin/PythonTestingToolsTaxonomy