如何从“python setup.py test”运行unittest发现?
我想弄清楚如何让python setup.py test
运行相当于python -m unittest discover
。 我不想使用run_tests.py脚本,我不想使用任何外部testing工具(如nose
或py.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是简单地扩展distutils
和setuptools
/ distribute
的test
命令。 这似乎是一个完整的方法,比我更喜欢的方式,但似乎正确地发现和运行python setup.py test
运行我的包中的所有python setup.py test
。 我坚持select这个作为我的问题的答案,希望有人会提供一个更优雅的解决scheme:)
示例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命令是唯一的消费者,并以指定的方式调用它。