Как мне запустить все модульные тесты Python в каталоге?
-
20-09-2019 - |
Вопрос
У меня есть каталог, содержащий мои модульные тесты Python.Каждый модуль модульного тестирования имеет вид тест_*.py.Я пытаюсь создать файл под названием all_test.py это, как вы уже догадались, запустит все файлы в вышеупомянутой тестовой форме и вернет результат.До сих пор я пробовал два метода;оба потерпели неудачу.Я покажу два метода, и я надеюсь, что кто-нибудь там знает, как на самом деле сделать это правильно.
Для моей первой отважной попытки я подумал: "Если я просто импортирую все мои модули тестирования в файл, а затем вызову это unittest.main()
блин, это сработает, верно?" Что ж, оказывается, я был неправ.
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?Или как мне в принципе заставить его работать, чтобы я мог просто запустить этот файл и при этом запустить все модульные тесты в этом каталоге?
Решение
Вы могли бы использовать тестовый прогон, который сделал бы это за вас. нос это очень хорошо, например.При запуске он найдет тесты в текущем дереве и запустит их.
Обновленный:
Вот немного кода из моих дней до появления носа.Вероятно, вам не нужен явный список имен модулей, но, возможно, остальное будет вам полезно.
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.Загрузчик тестов.откройте для себя.
import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)
runner = unittest.TextTestRunner()
runner.run(suite)
В python 3, если вы используете unittest.TestCase
:
- У вас должен быть пустой (или иной)
__init__.py
файл в вашемtest
каталог (должен быть названнымtest/
) - Ваши тестовые файлы внутри
test/
соответствуйте шаблонуtest_*.py
.Они могут находиться внутри подкаталога вtest/
, и эти дочерние файлы могут быть названы как угодно.
Затем вы можете запустить все тесты с помощью:
python -m unittest
Сделано!Решение размером менее 100 строк.Надеюсь, еще один новичок в python сэкономит время, найдя это.
Хорошо, немного изучив приведенный выше код (в частности, используя 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)
Да, вероятно, проще просто использовать nose, чем делать это, но это не главное.
Если вы хотите запустить все тесты из различных классов тестовых примеров и готовы указать их явно, вы можете сделать это следующим образом:
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
.
Я использовал 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, то выполняются все тесты.Если я прошу повторить только определенные или неудачные тесты, то выполняются только эти тесты.Это также не мешает моему тестировщику командной строки (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
по крайней мере, файл.Это позволяет тесту загружать вложенные модули, заменяя косую черту (или обратную косую черту) точками (см. replace_slash_by_dot
).
Поскольку тестовое обнаружение кажется законченной темой, существует некоторая специальная платформа для тестового обнаружения :
Подробнее читайте здесь : https://wiki .python.org/moin/PythonTestingToolsTaxonomy
Этот скрипт BASH выполнит тестовый каталог python unittest из ЛЮБОГО места файловой системы, независимо от того, в каком рабочем каталоге вы находитесь:его рабочий каталог всегда находится там, где это test
каталог находится.
ВСЕ ТЕСТЫ, независимые $PWD
модуль unittest Python чувствителен к вашему текущему каталогу, если вы не укажете ему, где (используя 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 <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
файл, который будет обременять ваш пакет / нагрузку на память во время производства.
Вот мой подход к созданию обертка для запуска тестов из командной строки:
#!/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())
Для простоты, пожалуйста, извините мое не-ПЕП8 стандарты кодирования.
Затем вы можете создать класс 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