如何从“python setup.py test”运行unittest发现?

我想弄清楚如何让python setup.py test运行相当于python -m unittest discover 。 我不想使用run_tests.py脚本,我不想使用任何外部testing工具(如nosepy.test )。 如果解决scheme只适用于python 2.7,那就没问题了。

setup.py ,我想我需要添加一些东西到test_suite和/或test_loader字段在configuration中,但我似乎无法find一个工作正常的组合:

 config = { 'name': name, 'version': version, 'url': url, 'test_suite': '???', 'test_loader': '???', } 

这可能只使用unit testing内置到Python 2.7?

仅供参考,我的项目结构如下所示:

 project/ package/ __init__.py module.py tests/ __init__.py test_module.py run_tests.py <- I want to delete this setup.py 

更新 :这是可能的unittest2但我想find一些相当于使用unittest东西

从https://pypi.python.org/pypi/unittest2

unittest2包含一个非常基本的setuptools兼容testing收集器。 在setup.py中指定test_suite ='unittest2.collector'。 这将使用包含setup.py的目录中的默认参数开始testing发现,因此它可能是最有用的一个示例(请参阅unittest2 / collector.py)。

现在,我只是使用了一个名为run_tests.py的脚本,但是我希望能够通过移动到只使用python setup.py test的解决scheme来摆脱这个问题。

这是我希望删除的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) 

从构build和分发包到Setuptools (重点是我的):

test_suite

一个命名unittest.TestCase子类的string(或包含其中的一个或多个的包或模块,或这样的子类的方法),或命名可以不带参数调用的函数并返回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', ... ) 

如果您使用py27 +或py32 +,解决scheme非常简单:

 test_suite="tests", 

你不需要configuration来获得这个工作。 基本上有两种主要的方法来做到这一点:

快捷的方式

将你的test_module.py重命名为module_test.py (基本上把_test作为后缀来testing一个特定的模块),python会自动find它。 只要确保将其添加到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 testing脚本test_module ,然后创build一个函数来运行testing。 在tests/__init__.py ,input如下所示的内容:

 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脚本。 您可能已经这样做了,但是如果您没有这样做,则可以包含父path,以便可以导入package模块和module脚本。 在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模块并运行您创build的命令my_module_suite

 from setuptools import setup, find_packages setup( ... test_suite = 'tests.my_module_suite', ... ) 

然后你只需运行python setup.py test

这是一个作为参考的样本 。

一个可能的解决scheme是简单地扩展distutilssetuptools / distributetest命令。 这似乎是一个完整的方法,比我更喜欢的方式,但似乎正确地发现和运行python setup.py test运行我的包中的所有python setup.py test 。 我坚持select这个作为我的问题的答案,希望有人会提供一个更优雅的解决scheme:)

(受启发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) 

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, }, ) 

另一个不太理想的解决scheme略有启发http://hg.python.org/unittest2/file/2b6411b9a838/unittest2/collector.py

添加一个返回已发现testing的TestSuite的模块。 然后configuration设置来调用该模块。

 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) 

这不会删除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的语义,但setuptoolstesting命令是唯一的消费者,并以指定的方式调用它。

Interesting Posts