85

我试图弄清楚如何python setup.py test运行相当于python -m unittest discover. 我不想使用 run_tests.py 脚本,也不想使用任何外部测试工具(如noseor py.test)。如果该解决方案仅适用于 python 2.7 就可以了。

setup.py中,我认为我需要在配置中的test_suite和/或test_loader字段中添加一些内容,但我似乎找不到正确工作的组合:

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

这是否可能仅使用unittest内置于 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但我想找到等效的东西unittest

来自https://pypi.python.org/pypi/unittest2

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

目前,我只使用了一个名为 的脚本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)
4

7 回答 7

46

如果你使用 py27+ 或 py32+,解决方案非常简单:

test_suite="tests",
于 2014-02-12T11:13:28.683 回答
41

使用 Setuptools 构建和分发包(重点是我的):

测试套件

命名 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',
    ...
)
于 2016-05-04T16:42:52.583 回答
20

您不需要配置即可使其正常工作。基本上有两种主要的方法来做到这一点:

快捷方式

将您的重命名test_module.pymodule_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和您的单元测试脚本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脚本。您可能已经这样做了,但万一您没有这样做,您可以包含父路径,以便您可以导入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模块并运行您创建的命令my_module_suite

from setuptools import setup, find_packages

setup(
    ...
    test_suite = 'tests.my_module_suite',
    ...
)

然后你就跑python setup.py test

这是某人作为参考制作的示例。

于 2014-04-26T06:26:17.053 回答
6

一种可能的解决方案是简单地扩展test命令 fordistutilssetuptools/ 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)
于 2013-06-08T21:59:17.017 回答
3

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,
    },
)
于 2014-05-03T10:17:06.393 回答
3

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

添加一个返回TestSuite发现的测试的模块。然后配置 setup 以调用该模块。

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)
于 2013-06-08T22:16:20.760 回答
0

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

于 2015-01-13T21:01:30.277 回答