Comment puis-je exécuter tous les tests unitaires Python dans un répertoire?
-
20-09-2019 - |
Question
J'ai un répertoire qui contient mes tests unitaires Python. Chaque module de test unitaire est de la forme Test _ *. Py . Je cherche à faire un fichier appelé all_test.py cette volonté, vous l'aurez deviné, exécutez tous les fichiers sous forme de test ci-dessus et retourner le résultat. J'ai essayé deux méthodes à ce jour; les deux ont échoué. Je vais montrer les deux méthodes, et j'espère que quelqu'un sait comment là réellement faire cela correctement.
Pour ma première tentative courageuse, je pensais: « Si j'importer simplement tous mes modules de test dans le fichier, puis appeler cette doodad de unittest.main()
, il fonctionnera, non? » Eh bien, tourne que j'avais 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 ne fonctionne pas, le résultat que je suis arrivé était:
$ python all_test.py
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
Pour mon deuxième essai, je pensais, ok, peut-être que je vais essayer de faire cette chose d'essai tout en plus la mode « manuel ». Donc, je tentais 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()
aussi ne fonctionnait pas, 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 de quelque sorte, 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 comme ça devrait être run=2
, mais il est un progrès. Mais comment puis-je passer et afficher le résultat principal? Ou comment puis-je le faire fonctionner essentiellement je peux simplement exécuter ce fichier, et ce faisant, exécuter tous les tests unitaires dans ce répertoire?
La solution
Vous pouvez utiliser un coureur de test qui ferait cela pour vous. est très bon par exemple. Lorsqu'il est exécuté, il trouvera des tests dans l'arborescence en cours et de les exécuter.
Mise à jour:
Voici un code de mes jours pré-nez. Vous ne voulez probablement pas la liste explicite des noms de modules, mais peut-être que le reste sera utile pour vous.
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)
Autres conseils
Avec Python 2.7 et plus vous n'avez pas d'écrire un nouveau code ou utiliser des outils tiers pour le faire; exécution d'un test récursif 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 le python 2.7 ou python 3.x unittest documentation.
Il est désormais possible directement à partir unittest: unittest.TestLoader. découvrir .
import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)
runner = unittest.TextTestRunner()
runner.run(suite)
En python 3, si vous utilisez unittest.TestCase
:
- Vous devez avoir un vide (ou autre) fichier
__init__.py
dans votre répertoiretest
( doit être nommétest/
) - Vos fichiers de test à l'intérieur
test/
correspondent au modèletest_*.py
. Ils peuvent être à l'intérieur d'un sous-répertoiretest/
, et ces subdirs peut être nommé comme quoi que ce soit.
Ensuite, vous pouvez exécuter tous les tests avec:
python -m unittest
Fait! Une solution à moins de 100 lignes. Si tout va bien un autre débutant python fait gagner du temps en trouvant cela.
Et bien en étudiant le code ci-dessus un peu (en particulier en utilisant TextTestRunner
et defaultTestLoader
), je suis en mesure d'obtenir assez proche. Finalement, je fixe mon code par également de passage toutes les suites de test à un seul constructeur de suites, plutôt que de les ajouter « manuellement », qui fixe 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 à utiliser juste le nez que pour ce faire, mais qui est d'ailleurs le point.
Si vous voulez exécuter tous les tests de différentes classes de cas de test et 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 sous-classes de TestCase
.
Je l'ai utilisé la méthode discover
et une surcharge de load_tests
pour atteindre ce résultat dans un (minimum, je pense) numéroter les 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 __name__ == '__main__':
unittest.main()
Exécution sur fives quelque chose comme
Ran 27 tests in 0.187s
OK
J'ai essayé différentes approches, mais tous semblent défectueux ou je dois maquillage un peu de code, qui est ennuyeux. Mais il y a un moyen convinient sous Linux, qui est tout simplement de trouver tous les tests par certain modèle, puis les appeler un par un.
find . -name 'Test*py' -exec python '{}' \;
et surtout, cela fonctionne certainement.
En cas de emballés bibliothèque ou une application, vous ne voulez pas le faire. setuptools
va le faire pour vous .
Pour utiliser cette commande, votre tests de projet doivent être emballés dans une suite de test de
unittest
soit par une fonction, une classe de TestCase ou d'une méthode, ou un module ou package contenant des classes deTestCase
. Si la suite du nom est un module, et le module a une fonctionadditional_tests()
, elle est appelée et le résultat (qui doit être ununittest.TestSuite
) est ajouté aux tests à exécuter. Si la suite du nom est un paquet, et les sous-modules sont récursive ajoutés sous-paquets à la suite de tests d'ensemble .
Il suffit de lui dire où votre package de test racine est, comme:
setup(
# ...
test_suite = 'somepkg.test'
)
run python setup.py test
.
découverte basée sur les fichiers peut être problématique en Python 3, à moins que vous évitez les importations relatives dans votre suite de tests, parce que discover
utilise l'importation de fichiers. Même si elle prend en charge top_level_dir
en option, mais j'ai eu quelques erreurs de récursion infinie. Ainsi, une solution simple pour un code non emballé est de mettre ce qui suit dans __init__.py
de votre package de test (voir Protocole load_tests Les).
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'ont pas vraiment compris comment exécuter tous les tests à la fois de l'interface graphique. (Edit: vous cliquez droit sur le dossier de test de racine et choisissez Run as -> Python unit-test
Voici ma solution actuelle:
import unittest
def load_tests(loader, tests, pattern):
return loader.discover('.')
if __name__ == '__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 alors tous les tests sont exécutés. Si je demande seulement répéter des tests spécifiques ou échoué alors que les tests sont exécutés. Il n'a pas interférer avec mon coureur de test (soit commandline nosetests) -. Elle est ignorée
Vous devrez peut-être modifier les arguments pour discover
en fonction de la configuration de votre projet.
Selon la réponse de Stephen Cagle J'ai ajouté le support des 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 attend chaque *Tests.py
contienne une seule *Tests(unittest.TestCase)
de classe qui est chargé à son tour et exécuté un après l'autre.
Cela fonctionne avec imbrications arbitraire des répertoires / modules, mais chaque répertoire doit contenir entre un fichier __init__.py
vide au moins. Cela permet à l'essai pour charger les modules emboîtés par le remplacement des barres obliques (ou barres obliques inverses) par des points (voir replace_slash_by_dot
).
Parce que la découverte de test semble être un sujet complet, il y a un cadre dédié à la découverte tester:
En savoir plus lire ici: https://wiki.python.org/moin/PythonTestingToolsTaxonomy
Ce script BASH exécutera le python unittest répertoire test partout dans le système de fichiers, peu importe quel répertoire de travail que vous êtes dans:. Son répertoire de travail toujours où ce répertoire test
est situé
tous les tests, indépendants PWD $
unittest le module Python est sensible à votre répertoire en cours, à moins que vous dire où (en utilisant l'option discover -s
).
Ceci est utile lors d'un séjour dans le ./src
ou répertoire de travail ./example
et 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
Les tests sélectionnés, PWD indépendant $
Je baptise ce fichier utilitaire: runone.py
et l'utiliser 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)
Pas besoin d'un fichier test/__init__.py
à la charge de votre colis / mémoire en tête lors de la production.
Voici mon approche en créant un emballage pour exécuter les tests 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é, s'il vous plaît excuser mon pep8 de codage des normes .
Ensuite, vous pouvez créer la classe BaseTest pour les composants communs pour tous vos tests, donc chacun de votre test serait simplement ressembler à:
from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
def test_foo(self):
driver = self.driver
driver.get(self.base_url + "/")
Pour exécuter, vous spécifiant simplement des tests dans le cadre des arguments de ligne de commande, par exemple:.
./run_tests.py -h http://example.com/ tests/**/*.py