Comment puis-je exécuter tous les tests de l'unité Python dans un répertoire?
j'ai un répertoire qui contient les tests de mon unité Python. Chaque module d'essai unitaire est du formulaire test_*.py . J'essaie de faire un fichier appelé all_test.py qui va, vous l'avez deviné, exécuter tous les fichiers dans le formulaire de test susmentionné et retourner le résultat. J'ai essayé deux méthodes jusqu'à présent; toutes deux ont échoué. Je vais vous montrer deux méthodes, et j'espère que quelqu'un ici sait comment le faire correctement.
pour ma première tentative vaillante, j'ai pensé "si j'importais tous mes modules de test dans le fichier, et que j'appelais ce doodad unittest.main()
, ça marcherait, Non?"Eh bien, il s'avère que j'ai eu tort.
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 __name__ == "__main__":
unittest.main()
cela n'a pas fonctionné, le résultat que j'ai obtenu était:
$ python all_test.py
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
pour mon second essai, je me suis dit, ok, peut-être que je vais essayer de faire tout ce test d'une 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 __name__ == "__main__":
unittest.main()
This aussi n'a pas fonctionné, mais il 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
il me semble avoir une suite, et je peux exécuter le résultat. Je suis un peu préoccupé par le fait qu'il dit que je n'ai que run=1
, semble que cela devrait être run=2
, mais c'est un progrès. Mais comment passer et afficher le résultat sur main? Ou comment faire essentiellement le faire fonctionner donc je ne peux il suffit d'exécuter ce fichier, et ce faisant, d'exécuter les tests unitaires dans ce répertoire?
14 réponses
vous pourriez utiliser un coureur de test qui ferait cela pour vous. nez est très bon par exemple. Une fois exécuté, il trouvera des tests dans l'arbre courant et les exécutera.
mise à Jour:
voici un code de mes jours de pré-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 supérieur, vous n'avez pas besoin d'écrire de nouveau code ou d'utiliser des outils tiers pour le faire; l'exécution de test récursive 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 lire plus dans le python 2.7 ou python 3.x unittest de la documentation.
cela est maintenant possible directement à partir de unittest: unittest.TestLoader.découvrez .
import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)
runner = unittest.TextTestRunner()
runner.run(suite)
bien en étudiant le code ci-dessus un peu (spécifiquement en utilisant TextTestRunner
et defaultTestLoader
), j'ai pu me rapprocher. Finalement j'ai corrigé mon code en passant aussi toutes les suites de test à un constructeur de suites simples, plutôt que de les ajouter "manuellement", ce qui a corrigé 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 faire ceci, mais c'est sans compter le point.
si vous voulez exécuter tous les tests à partir de différentes classes de cas de test et que vous êtes heureux de Les spécifier explicitement, alors vous pouvez le faire comme ceci:
from unittest import TestLoader, TextTestRunner, TestSuite
from uclid.test.test_symbols import TestSymbols
from uclid.test.test_patterns import TestPatterns
if __name__ == "__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
.
en python 3, Si vous utilisez unittest.TestCase
et que vous avez un fichier __init__.py
vide (ou non) dans votre répertoire test, alors vous pouvez exécuter tous les tests avec
python -m unittest
fait! Une solution de moins de 100 lignes. Espérons qu'un autre débutant de python gagne du temps en trouvant ceci.
j'ai utilisé la méthode discover
et une surcharge de load_tests
pour obtenir ce résultat dans un (minime, je pense) lignes de nombre 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 __name__ == '__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 un code, c'est ennuyeux. Mais il y a un moyen convaincant sous linux, c'est simplement de trouver chaque test à travers un certain modèle et ensuite de les invoquer un par un.
find . -name 'Test*py' -exec python '{}' \;
et le plus important, il fonctionne certainement.
dans le cas d'une bibliothèque ou d'une application empaquetée , vous ne voulez pas le faire. setuptools
le fera pour vous .
pour utiliser cette commande, les tests de votre projet doivent être enveloppés dans une suite de tests
unittest
par une fonction, une classe ou une méthode TestCase, ou un module ou un paquet contenant les classesTestCase
. Si la suite nommée est un module, et que le module a une fonctionadditional_tests()
, il est appelée et le résultat (qui doit être ununittest.TestSuite
) est ajouté pour les tests à exécuter. Si la suite nommée est un paquet, tous les sous-modules et sous-paquets sont ajoutés récursivement à la suite de test globale .
il suffit de lui dire où votre paquet de test racine est, comme:
setup(
# ...
test_suite = 'somepkg.test'
)
et python setup.py test
.
la découverte basée sur des fichiers peut être problématique en Python 3, à moins que vous n'évitiez importations relatives dans votre suite de test, parce que discover
utilise l'importation de fichier. Même s'il supporte l'option top_level_dir
, mais j'ai eu quelques erreurs de récursion infinies. Ainsi, une solution simple pour un code non empaqueté est de mettre ce qui suit dans __init__.py
de votre paquet test (voir load_tests Protocol ).
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 n'ai pas vraiment compris comment exécuter tous les tests à la fois à partir de L'interface graphique. (modifier: vous faites un clic droit sur le dossier de test racine et choisissez Run as -> Python unit-test
C'est ma solution actuelle:
import unittest
def load_tests(loader, tests, pattern):
return loader.discover('.')
if __name__ == '__main__':
unittest.main()
j'ai mis ce code dans un module appelé all
dans mon répertoire test. Si J'exécute ce module comme un unittest à partir de LiClipse, alors tous les tests sont exécutés. Si je demande seulement de répéter des tests spécifiques ou échoués, alors seulement ces tests sont exécuter. Il n'interfère pas non plus avec mon coureur de test en ligne de commande (nosetests) -- il est ignoré.
vous pourriez avoir besoin de changer les arguments en discover
basé sur 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 tous les sous-répertoires de .
pour 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 l'imbrication profonde arbitraire de répertoires / modules, mais chaque répertoire entre les deux 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 obliques) par des Points (voir replace_slash_by_dot
).
parce que la découverte de Test semble être un sujet complet, il existe un cadre dédié à la découverte de test:
Lire plus ici: https://wiki.python.org/moin/PythonTestingToolsTaxonomy
ce script BASH exécutera le répertoire de test unittest de python de N'importe où dans le système de fichiers, quel que soit le répertoire de travail dans lequel vous vous trouvez: son répertoire de travail sera toujours là où se trouve le répertoire test
.
TOUS les ESSAIS, indépendant, $PWD
le module Python unittest est sensible à votre répertoire courant, à moins que vous ne lui disiez où (en utilisant l'option discover -s
).
Ceci est utile pour rester dans le répertoire de travail ./src
ou ./example
et vous avez besoin d'un test rapide de l'ensemble de l'unité:
#!/bin/bash
this_program=""151900920""
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"
python -m unittest discover -s "$readlink"/test -v
SELECTED TESTS, independent $PWD
je nomme ce fichier d'utilité: runone.py
et l'utiliser comme ceci:
runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash
this_program=""151920920""
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"
(cd "$dirname"/test; python -m unittest )
pas besoin d'un fichier test/__init__.py
pour surcharger votre paquet/mémoire-overhead pendant la production.
Voici mon approche en créant un wrapper pour exécuter des tests à partir de la ligne de commande:
#!/usr/bin/env python3
import os, sys, unittest, argparse, inspect, logging
if __name__ == '__main__':
# Parse arguments.
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("-?", "--help", action="help", help="show this help message and exit" )
parser.add_argument("-v", "--verbose", action="store_true", dest="verbose", help="increase output verbosity" )
parser.add_argument("-d", "--debug", action="store_true", dest="debug", help="show debug messages" )
parser.add_argument("-h", "--host", action="store", dest="host", help="Destination host" )
parser.add_argument("-b", "--browser", action="store", dest="browser", help="Browser driver.", choices=["Firefox", "Chrome", "IE", "Opera", "PhantomJS"] )
parser.add_argument("-r", "--reports-dir", action="store", dest="dir", help="Directory to save screenshots.", default="reports")
parser.add_argument('files', nargs='*')
args = parser.parse_args()
# Load files from the arguments.
for filename in args.files:
exec(open(filename).read())
# See: http://codereview.stackexchange.com/q/88655/15346
def make_suite(tc_class):
testloader = unittest.TestLoader()
testnames = testloader.getTestCaseNames(tc_class)
suite = unittest.TestSuite()
for name in testnames:
suite.addTest(tc_class(name, cargs=args))
return suite
# Add all tests.
alltests = unittest.TestSuite()
for name, obj in inspect.getmembers(sys.modules[__name__]):
if inspect.isclass(obj) and name.startswith("FooTest"):
alltests.addTest(make_suite(obj))
# Set-up logger
verbose = bool(os.environ.get('VERBOSE', args.verbose))
debug = bool(os.environ.get('DEBUG', args.debug))
if verbose or debug:
logging.basicConfig( stream=sys.stdout )
root = logging.getLogger()
root.setLevel(logging.INFO if verbose else logging.DEBUG)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.INFO if verbose else logging.DEBUG)
ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
root.addHandler(ch)
else:
logging.basicConfig(stream=sys.stderr)
# Run tests.
result = unittest.TextTestRunner(verbosity=2).run(alltests)
sys.exit(not result.wasSuccessful())
par souci de simplicité, veuillez excuser mon Non - PEP8 normes de codage.
alors vous pouvez créer la classe BaseTest pour les composants communs pour tous vos tests, donc chacun de vos tests ressemblerait simplement à:
from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
def test_foo(self):
driver = self.driver
driver.get(self.base_url + "/")
pour exécuter, vous spécifiez simplement des tests dans le cadre de la arguments en ligne de commande, par exemple:
./run_tests.py -h http://example.com/ tests/**/*.py