Как запустить unittest обнаружить из "теста python setup.py"?
Я пытаюсь выяснить, как получить python setup.py test
для запуска эквивалента python -m unittest discover
. Я не хочу использовать run_tests.py script, и я не хочу использовать внешние инструменты тестирования (например, nose
или py.test
). Это нормально, если решение работает только на python 2.7.
В setup.py
, я думаю, мне нужно добавить что-то в поля test_suite
и/или test_loader
в config, но я не могу найти подходящую комбинацию:
config = {
'name': name,
'version': version,
'url': url,
'test_suite': '???',
'test_loader': '???',
}
Возможно ли это, используя только unittest
, встроенный в python 2.7?
FYI, моя структура проекта выглядит следующим образом:
project/
package/
__init__.py
module.py
tests/
__init__.py
test_module.py
run_tests.py <- I want to delete this
setup.py
Обновить. Это возможно с помощью unittest2
, но я хочу найти что-то эквивалентное, используя только unittest
От https://pypi.python.org/pypi/unittest2
unittest2 включает в себя очень простой сборщик тестов, совместимый с setuptools. Укажите test_suite = 'unittest2.collector' в файле setup.py. Это запускает тестовое обнаружение с параметрами по умолчанию из каталога, содержащего setup.py, поэтому он, пожалуй, наиболее полезен в качестве примера (см. Unittest2/collector.py).
В настоящее время я просто использую script, называемый run_tests.py
, но я надеюсь, что смогу избавиться от него, перейдя к решению, которое использует только python setup.py test
.
Здесь run_tests.py
Я надеюсь удалить:
import unittest
if __name__ == '__main__':
# use the default shared TestLoader instance
test_loader = unittest.defaultTestLoader
# use the basic test runner that outputs to sys.stderr
test_runner = unittest.TextTestRunner()
# automatically discover all tests in the current dir of the form test*.py
# NOTE: only works for python 2.7 and later
test_suite = test_loader.discover('.')
# run the test suite
test_runner.run(test_suite)
Ответы
Ответ 1
От Построение и распространение пакетов с помощью Setuptools (внимание):
test_suite
Строка, обозначающая подкласс unittest.TestCase(или пакет или модуль содержащий один или несколько из них, или способ такого подкласса), или именование функцию, которая может быть вызвана без аргументов и возвращает unittest.TestSuite.
Следовательно, в setup.py
вы добавили бы функцию, которая возвращает TestSuite:
import unittest
def my_test_suite():
test_loader = unittest.TestLoader()
test_suite = test_loader.discover('tests', pattern='test_*.py')
return test_suite
Затем вы должны указать команду setup
следующим образом:
setup(
...
test_suite='setup.my_test_suite',
...
)
Ответ 2
Если вы используете py27 + или py32 +, решение довольно просто:
test_suite="tests",
Ответ 3
Вам не нужна конфигурация, чтобы заставить эту работу работать. В основном это два основных способа:
Быстрый способ
Переименуйте test_module.py
в module_test.py
(в основном добавьте _test
в качестве суффикса для тестов для определенного модуля), и python найдет его автоматически. Просто добавьте это в setup.py
:
from setuptools import setup, find_packages
setup(
...
test_suite = 'tests',
...
)
Длинный путь
Здесь, как это сделать с вашей текущей структурой каталогов:
project/
package/
__init__.py
module.py
tests/
__init__.py
test_module.py
run_tests.py <- I want to delete this
setup.py
В разделе tests/__init__.py
вы хотите импортировать unittest
и ваш unit test script test_module
, а затем создать функцию для запуска тестов. В tests/__init__.py
введите что-то вроде этого:
import unittest
import test_module
def my_module_suite():
loader = unittest.TestLoader()
suite = loader.loadTestsFromModule(test_module)
return suite
Класс TestLoader
имеет другие функции помимо loadTestsFromModule
. Вы можете запустить dir(unittest.TestLoader)
, чтобы увидеть другие, но этот самый простой в использовании.
Поскольку ваша структура каталогов такова, вы, вероятно, захотите, чтобы test_module
мог импортировать ваш module
script. Возможно, вы уже это сделали, но на всякий случай вы этого не сделали, вы можете включить родительский путь, чтобы вы могли импортировать модуль package
и module
script. В верхней части test_module.py
введите:
import os, sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import unittest
import package.module
...
Затем, наконец, в setup.py
включите модуль tests
и запустите команду, которую вы создали, my_module_suite
:
from setuptools import setup, find_packages
setup(
...
test_suite = 'tests.my_module_suite',
...
)
Затем вы просто запустите python setup.py test
.
Вот пример , сделанный в качестве ссылки.
Ответ 4
Одно из возможных решений - просто расширить команду test
для distutils
и setuptools
/distribute
. Это похоже на общее количество и сложнее, чем я бы предпочел, но, кажется, правильно обнаруживает и запускает все тесты в моем пакете при запуске python setup.py test
. Я стараюсь выбрать это как ответ на мой вопрос в надежде, что кто-то предоставит более элегантное решение:)
(Вдохновленный https://docs.pytest.org/en/latest/goodpractices.html#integrating-with-setuptools-python-setup-py-test-pytest-runner)
Пример setup.py
:
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
def discover_and_run_tests():
import os
import sys
import unittest
# get setup.py directory
setup_file = sys.modules['__main__'].__file__
setup_dir = os.path.abspath(os.path.dirname(setup_file))
# use the default shared TestLoader instance
test_loader = unittest.defaultTestLoader
# use the basic test runner that outputs to sys.stderr
test_runner = unittest.TextTestRunner()
# automatically discover all tests
# NOTE: only works for python 2.7 and later
test_suite = test_loader.discover(setup_dir)
# run the test suite
test_runner.run(test_suite)
try:
from setuptools.command.test import test
class DiscoverTest(test):
def finalize_options(self):
test.finalize_options(self)
self.test_args = []
self.test_suite = True
def run_tests(self):
discover_and_run_tests()
except ImportError:
from distutils.core import Command
class DiscoverTest(Command):
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
discover_and_run_tests()
config = {
'name': 'name',
'version': 'version',
'url': 'http://example.com',
'cmdclass': {'test': DiscoverTest},
}
setup(**config)
Ответ 5
Стандартная библиотека Python unittest
модуль поддерживает обнаружение (в Python 2.7 и более поздних версиях и Python 3.2 и более поздних). Если вы можете принять эти минимальные версии, вы можете просто добавить аргумент командной строки discover
в команду unittest
.
Для setup.py
требуется только небольшая настройка:
import setuptools.command.test
from setuptools import (find_packages, setup)
class TestCommand(setuptools.command.test.test):
""" Setuptools test command explicitly using test discovery. """
def _test_args(self):
yield 'discover'
for arg in super(TestCommand, self)._test_args():
yield arg
setup(
...
cmdclass={
'test': TestCommand,
},
)
Ответ 6
Другим менее идеальным решением, слегка вдохновленным http://hg.python.org/unittest2/file/2b6411b9a838/unittest2/collector.py
Добавьте модуль, который возвращает TestSuite
обнаруженных тестов. Затем настройте настройку для вызова этого модуля.
project/
package/
__init__.py
module.py
tests/
__init__.py
test_module.py
discover_tests.py
setup.py
Здесь discover_tests.py
:
import os
import sys
import unittest
def additional_tests():
setup_file = sys.modules['__main__'].__file__
setup_dir = os.path.abspath(os.path.dirname(setup_file))
return unittest.defaultTestLoader.discover(setup_dir)
И здесь setup.py
:
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
config = {
'name': 'name',
'version': 'version',
'url': 'http://example.com',
'test_suite': 'discover_tests',
}
setup(**config)
Ответ 7
Это не приведет к удалению run_tests.py, но заставит его работать с setuptools. Добавить:
class Loader(unittest.TestLoader):
def loadTestsFromNames(self, names, _=None):
return self.discover(names[0])
Затем в setup.py: (я предполагаю, что вы делаете что-то вроде setup(**config)
)
config = {
...
'test_loader': 'run_tests:Loader',
'test_suite': '.', # your start_dir for discover()
}
Единственный недостаток, который я вижу, это сгибание семантики loadTestsFromNames
, но команда testtools test является единственным пользователем и вызывает ее в указанном пути.