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

cdw*_*son 72 python unit-testing nose pytest unittest2

我正在试图找出如何python setup.py test运行等效的python -m unittest discover.我不想使用run_tests.py脚本,我不想使用任何外部测试工具(如nosepy.test).如果解决方案仅适用于python 2.7,那就没问题.

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

config = {
    'name': name,
    'version': version,
    'url': url,
    'test_suite': '???',
    'test_loader': '???',
}
Run Code Online (Sandbox Code Playgroud)

这可能只使用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
Run Code Online (Sandbox Code Playgroud)

更新:这是可能的,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)
Run Code Online (Sandbox Code Playgroud)

sas*_*hpe 40

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

test_suite="tests",
Run Code Online (Sandbox Code Playgroud)

  • 这是正确的解决方案。我没有问题@CharlesL。有。我所有的测试都被命名为`test_*.py`。此外,我发现它实际上会递归搜索给定的目录以查找扩展`unittest.TestCast`的*任何*类。如果您有一个目录结构,其中包含 `tests/first_batch/test_*.py` 和 `tests/second_batch/test_*.py`,这将非常有用。您只需指定`test_suite="tests",`,它就会递归地获取所有内容。请注意,每个嵌套目录中都需要有一个 `__init__.py` 文件。 (2认同)

Mic*_*udl 35

使用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
Run Code Online (Sandbox Code Playgroud)

然后,您将指定命令setup,如下所示:

setup(
    ...
    test_suite='setup.my_test_suite',
    ...
)
Run Code Online (Sandbox Code Playgroud)

  • 此解决方案存在问题,因为它创建了单元测试的2个"级别".这意味着setuptools将创建一个'test'命令,尝试从setup.my_test_suite创建一个TestSuite,这将强制它导入setup.py,这将再次运行setup()!第二次它将创建一个运行所需测试的新(嵌套)测试命令.这对大多数人来说可能并不明显,但是如果你试图扩展测试命令(我需要修改它,因为我无法在原地运行我的测试'),你可能会遇到奇怪的问题.请改用/sf/answers/1520843061/ (3认同)
  • 由于上述原因,这导致测试为我运行两次.通过将函数移动到tests文件夹的`__init __.py`并引用它来修复它. (2认同)
  • 可以通过在setup.py脚本中的if __name__ =='__main __':`块内执行`setup()`函数来轻松解决两次执行测试的问题。第一次执行安装脚本,因此将调用if块;第二次将安装脚本作为模块导入,因此不会调用if块。 (2认同)

ant*_*ter 18

你不需要配置来使这个工作.基本上有两种方法可以做到:

快捷的方式

重命名你test_module.pymodule_test.py(基本上添加_test为测试特定模块的后缀),python会自动找到它.只需确保将其添加到setup.py:

from setuptools import setup, find_packages

setup(
    ...
    test_suite = 'tests',
    ...
)
Run Code Online (Sandbox Code Playgroud)

漫长的道路

以下是使用当前目录结构的方法:

project/
  package/
    __init__.py
    module.py
  tests/
    __init__.py
    test_module.py
  run_tests.py <- I want to delete this
  setup.py
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
...
Run Code Online (Sandbox Code Playgroud)

最后,在setup.py,包含tests模块并运行您创建的命令,my_module_suite:

from setuptools import setup, find_packages

setup(
    ...
    test_suite = 'tests.my_module_suite',
    ...
)
Run Code Online (Sandbox Code Playgroud)

然后你就跑了python setup.py test.

以下是某人作为参考的示例.

  • 问题是如何使"python setup.py test"使用unittest的发现功能.这根本没有解决这个问题. (2认同)

cdw*_*son 5

一种可能的解决方案是简单地扩展和/的test命令。这似乎是一个大杂烩,而且比我希望的要复杂得多,但似乎在运行. 我推迟选择这个作为我问题的答案,希望有人能提供更优雅的解决方案:)distutilssetuptoolsdistributepython 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)
Run Code Online (Sandbox Code Playgroud)