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?

191
demandé sur Tonechas 2009-11-14 02:01:39

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)
91
répondu Ned Batchelder 2015-09-05 03:13:37

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.

323
répondu Travis Bear 2015-05-24 01:07:47

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)
34
répondu slaughter98 2018-07-26 16:06:12

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.

29
répondu Stephen Cagle 2016-09-19 14:15:35

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)

uclid est mon projet et TestSymbols et TestPatterns sont des sous-classes de TestCase .

23
répondu demented hedgehog 2018-03-18 01:34:33

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.

18
répondu tmck-code 2017-05-02 08:27:43

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
12
répondu rds 2012-02-16 13:04:49

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.

6
répondu zinking 2013-11-05 02:24:33

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 classes TestCase . Si la suite nommée est un module, et que le module a une fonction additional_tests() , il est appelée et le résultat (qui doit être un unittest.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
6
répondu saaj 2016-01-05 08:37:02

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.

4
répondu Dunes 2014-07-22 15:08:19

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 ).

1
répondu Peter Kofler 2017-05-23 12:26:35

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

1
répondu Bactisme 2014-09-15 08:00:01

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.

0
répondu Egbert S 2018-08-12 23:02:41

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
-1
répondu kenorb 2015-05-24 11:13:48