Как запустить все модульные тесты Python в каталоге?
у меня есть каталог, который содержит мои модульные тесты Python. Каждый модуль модульного теста имеет вид test_*. py. я пытаюсь создать файл с именем all_test.py это будет, как вы уже догадались, запустить все файлы в вышеупомянутой тестовой форме и вернуть результат. До сих пор я пробовал два метода; оба потерпели неудачу. Я покажу два метода, и я надеюсь, что кто-то там знает, как на самом деле сделать это правильно.
моя первая попытка, я подумал: "если я просто импортирую все мои тестовые модули в файл, а затем назову это unittest.main()
doodad, это будет работать, верно?- Ну, оказывается, я ошибался.
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()
это не сработало, в результате я получил:
$ python all_test.py
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
для моей второй попытки, я, хотя, хорошо, может быть, я попытаюсь сделать все это тестирование в более "ручной" манере. Поэтому я попытался сделать это ниже:
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()
это тоже не получилось, но, кажется, так близко!
$ 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
мне кажется, чтобы иметь какой-то набор, и я могу выполнить результат. Я немного обеспокоен тем, что он говорит, что у меня только run=1
, кажется, что должно быть run=2
, но это уже прогресс. Но как мне передать и отобразить результат в main? Или как мне в основном заставить его работать, чтобы я мог просто запустить этот файл, и при этом запустить все модульные тесты в этом каталоге?
14 ответов:
вы можете использовать тестовый бегун, который сделает это за вас. нос - Это очень хорошо, например. При запуске он найдет тесты в текущем дереве и запустит их.
обновление:
вот код из моих дней до носа. Вы, вероятно, не хотите явный список имен модулей, но, возможно, остальное будет полезно для вас.
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)
С Python 2.7 и выше, вам не нужно писать новый код или использовать сторонние инструменты для этого; выполнение рекурсивный тест через командную строку встроен.
python -m unittest discover <test_directory> # or python -m unittest discover -s <directory> -p '*_test.py'
вы можете прочитать больше в python 2.7 или python 3.x документация unittest.
теперь это возможно непосредственно из unittest:unittest.TestLoader.откройте для себя.
import unittest loader = unittest.TestLoader() start_dir = 'path/to/your/test/files' suite = loader.discover(start_dir) runner = unittest.TextTestRunner() runner.run(suite)
Ну, изучив код выше немного (в частности, с помощью
TextTestRunner
иdefaultTestLoader
), мне удалось подобраться довольно близко. В конце концов я исправил свой код, также просто передав все наборы тестов в один конструктор наборов, а не добавив их "вручную", что исправило мои другие проблемы. Так вот мое решение.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)
Да, вероятно, проще просто использовать нос, чем делать это, но это не имеет значения.
если вы хотите запустить все тесты из различных классов тестовых случаев, и вы рады указать их явно, то вы можете сделать это следующим образом:
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
мой проект иTestSymbols
иTestPatterns
являются подклассамиTestCase
.
в python 3, Если вы используете
unittest.TestCase
и у вас есть пустой (или иное)__init__.py
файл в вашем тестовом каталоге, то вы можете запустить все тесты сpython -m unittest
готово! Решение менее 100 строк. Надеюсь, еще один новичок python сэкономит время, найдя это.
я использовал
discover
метод и перегрузкаload_tests
чтобы достичь этого результата в (минимальном, я думаю) количестве строк кода: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()
исполнение на пятерки что-то вроде
Ran 27 tests in 0.187s OK
Я пробовал различные подходы, но все они кажутся ошибочными, или мне нужно создать какой-то код, это раздражает. Но есть удобный способ под linux, то есть просто найти каждый тест через определенный шаблон, а затем вызвать их один за другим.
find . -name 'Test*py' -exec python '{}' \;
и самое главное, это, безусловно, работает.
в случае упакованный библиотека или приложение, вы не хотите этого делать.
setuptools
сделает это за вас.чтобы использовать эту команду, тесты вашего проекта должно быть заключено в
unittest
набор тестов либо функцией, классом или методом TestCase, либо модулем или пакетом, содержащимTestCase
классы. Если именованный набор является модулем, и модуль имеетadditional_tests()
функция, она вызывается и результат (который должен бытьunittest.TestSuite
) является добавлено в тесты. Если именованный набор является пакетом, все подмодули и подпакеты рекурсивно добавляются в общий набор тестов.просто скажите ему, где находится ваш корневой тестовый пакет, например:
setup( # ... test_suite = 'somepkg.test' )
и работать
python setup.py test
.обнаружение на основе файлов может быть проблематичным в Python 3, Если вы не избегаете относительного импорта в своем наборе тестов, потому что
discover
использует импорт файла. Даже если он поддерживает необязательноtop_level_dir
, но у меня были некоторые бесконечные ошибки рекурсии. Таким образом, простое решение для неупакованного кода состоит в том, чтобы поместить следующее в__init__.py
вашего тестового пакета (см. протокол 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
Я использую PyDev / LiClipse и на самом деле не понял, как запустить все тесты сразу из графического интерфейса. (изменить: щелкните правой кнопкой мыши корневую папку теста и выберите
Run as -> Python unit-test
Это мой текущий обходной путь:
import unittest def load_tests(loader, tests, pattern): return loader.discover('.') if __name__ == '__main__': unittest.main()
Я поместил этот код в модуль под названием
all
в моем тестовом каталоге. Если я запускаю этот модуль как unittest от LiClipse, то все тесты выполняются. Если я попрошу только повторить определенные или неудачные тесты, то будут выполняться только эти тесты. Это не мешает мне командная строка Test runner либо (nosetests) -- это игнорируется.возможно, вам придется изменить аргументы на
discover
в зависимости от настроек проекта.
на основе ответа Стивен Кагла я добавил поддержку вложенных тестовых модулей.
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)
код выполняет поиск во всех подкаталогах
.
на*Tests.py
файлы, которые загружены. Он ожидает каждого*Tests.py
содержать один класс*Tests(unittest.TestCase)
который загружается по очереди и выполняется один за другим.это работает с произвольной глубокой вложенностью каталогов / модулей, но каждый каталог между ними должен содержать пустое
__init__.py
file at наименьший. Это позволяет тесту загружать вложенные модули, заменяя косые черты (или обратные косые черты) точками (см.replace_slash_by_dot
).
поскольку тестовое обнаружение кажется полным предметом, существует некоторая выделенная структура для тестирования обнаружения:
Подробнее читайте здесь:https://wiki.python.org/moin/PythonTestingToolsTaxonomy
этот скрипт BASH выполнит тестовый каталог python unittest из любой точки файловой системы, независимо от того, в каком рабочем каталоге вы находитесь: его рабочий каталог всегда будет там, где это - это.
все тесты, независимые $PWD
модуль unittest Python чувствителен к вашему текущему каталогу, если вы не скажете ему, где (используя ).
это полезно при пребывании в
./src
или./example
рабочий каталог и вам нужен быстрый общий модульный тест:#!/bin/bash this_program="" dirname="`dirname $this_program`" readlink="`readlink -e $dirname`" python -m unittest discover -s "$readlink"/test -v
выбранные тесты, независимые $PWD
Я называю этот файл утилиты:
runone.py
и использовать его как это:runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash this_program="" dirname="`dirname $this_program`" readlink="`readlink -e $dirname`" (cd "$dirname"/test; python -m unittest )
не нужен
test/__init__.py
файл для загрузки вашего пакета / памяти-накладные расходы во время производства.
вот мой подход к созданию фантик запуск тестов из командной строки:
#!/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())
для простоты, пожалуйста, извините мой не -PEP8 стандарты кодирования.
затем вы можете создать класс BaseTest для общих компонентов для всех ваших тестов, поэтому каждый из ваших тестов будет просто выглядеть так:
from BaseTest import BaseTest class FooTestPagesBasic(BaseTest): def test_foo(self): driver = self.driver driver.get(self.base_url + "/")
для запуска вы просто указываете тесты как часть аргументов командной строки, например:
./run_tests.py -h http://example.com/ tests/**/*.py