Как запустить все тестовые тесты Python в каталоге?
У меня есть каталог, содержащий мои модульные тесты Python. Каждый модуль unit test имеет форму 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
, но это прогресс. Но как передать и отобразить результат на главную? Или как я в основном его работаю, поэтому я могу просто запустить этот файл, и при этом запустите все модульные тесты в этом каталоге?
Ответы
Ответ 1
Вы можете использовать тестовый бегун, который сделает это за вас. nose очень хорошо, например. При запуске он найдет тесты в текущем дереве и запустит их.
Обновлено:
Вот какой код из моих досудных дней. Вероятно, вам не нужен явный список имен модулей, но, возможно, остальное вам будет полезно.
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)
Ответ 2
С Python 2.7 и выше вам не нужно писать новый код или использовать сторонние инструменты для этого; выполнение рекурсивного теста через командную строку встроено. Поместите __init__.py
в свой тестовый каталог и:
python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'
Вы можете прочитать больше в Python 2.7
или документация Python 3.x unittest.
Ответ 3
В Python 3, если вы используете unittest.TestCase
:
- У вас должен быть пустой (или другой) файл
__init__.py
в вашей test
директории (должен иметь имя test/
) - Ваши тестовые файлы внутри
test/
соответствуют шаблону test_*.py
. Они могут находиться внутри test/
подкаталога test/
, и эти подкаталоги могут называться как угодно.
Затем вы можете запустить все тесты с помощью:
python -m unittest
Готово! Решение менее 100 строк. Надеюсь, другой начинающий питон сэкономит время, найдя это.
Ответ 4
Теперь это возможно прямо из unittest: unittest.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)
Ответ 5
Ну, немного изучив код выше (в частности, используя 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)
Да, возможно, проще просто использовать нос, чем делать это, но это не относится к делу.
Ответ 6
Если вы хотите запустить все тесты из различных классов тестовых случаев и готовы указать их явно, вы можете сделать это следующим образом:
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
.
Ответ 7
Я использовал метод 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
Ответ 8
Я пробовал различные подходы, но все они кажутся ошибочными, или мне приходится составлять какой-то код, что раздражает. Но есть удобный способ под Linux, это просто найти каждый тест через определенный шаблон, а затем вызывать их один за другим.
find . -name 'Test*py' -exec python '{}' \;
и, самое главное, он определенно работает.
Ответ 9
В случае упакованной библиотеки или приложения вы не хотите этого делать. setuptools
сделает это за вас.
Чтобы использовать эту команду, ваши тесты проектов должны быть завернуты в набор тестов unittest
либо с помощью функции, класса или метода TestCase, либо с модулем или пакетом, содержащим классы TestCase
. Если именованный набор является модулем, а модуль имеет функцию additional_tests()
, он вызывается, и результат (который должен быть unittest.TestSuite
) добавляется к запускаемым тестам. Если именованный пакет является пакетом, любые подмодули и подпакеты рекурсивно добавляются в общий набор тестов.
Просто скажите, где ваш корневой тестовый пакет, например:
setup(
# ...
test_suite = 'somepkg.test'
)
И запустите python setup.py test
.
Поиск на основе файлов может быть проблематичным в Python 3, если вы не избежите относительного импорта в вашем тестовом наборе, потому что discover
использует импорт файлов, Несмотря на то, что он поддерживает необязательный top_level_dir
, но у меня были некоторые бесконечные ошибки рекурсии. Таким образом, простое решение для не-упакованного кода заключается в следующем: "rel= "nofollow" > 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
Ответ 10
Я использую 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, тогда все тесты будут запущены. Если я попрошу только повторить определенные или неудачные тесты, тогда будут выполняться только те тесты. Это не мешает моему тестировщику командной строки (nosetests) - он игнорируется.
Возможно, вам придется изменить аргументы на discover
на основе настройки вашего проекта.
Ответ 11
Основываясь на ответе Stephen Cagle, я добавил поддержку вложенных тестовых модулей.
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
файл. Это позволяет тесту загружать вложенные модули, заменяя косые черты (или обратные косые черты) точками (см. replace_slash_by_dot
).
Ответ 12
Поскольку обнаружение теста, похоже, является полным предметом, для проверки обнаружения существует некоторая выделенная среда:
Подробнее здесь: https://wiki.python.org/moin/PythonTestingToolsTaxonomy
Ответ 13
Этот BASH-скрипт выполнит тестовый каталог python unittest из ЛЮБОГО места в файловой системе, независимо от того, в каком рабочем каталоге вы находитесь: его рабочий каталог всегда будет находиться там, где находится этот test
каталог.
ВСЕ ИСПЫТАНИЯ, независимые $ PWD
Модуль Python unittest чувствителен к вашему текущему каталогу, если вы не discover -s
его где (используя опцию discover -s
).
Это полезно, когда вы остаетесь в рабочем каталоге ./src
или ./example
и вам нужен быстрый общий юнит-тест:
#!/bin/bash
this_program="$0"
dirname="'dirname $this_program'"
readlink="'readlink -e $dirname'"
python -m unittest discover -s "$readlink"/test -v
ИЗБРАННЫЕ ИСПЫТАНИЯ, независимые $ PWD
Я runone.py
этот служебный файл: runone.py
и использую его так:
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)
Нет необходимости в test/__init__.py
файле test/__init__.py
чтобы нагружать ваш пакет/накладные расходы памяти во время производства.
Ответ 14
Это старый вопрос, но то, что у меня сработало сейчас (в 2019 году):
python -m unittest *_test.py
Все мои тестовые файлы находятся в той же папке, что и исходные файлы, и заканчиваются на _test
.
Ответ 15
Вот мой подход, создав оболочку для запуска тестов из командной строки:
#!/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