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


80

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

在中setup.py,我想我需要在config的test_suiteand和/或test_loader字段中添加一些内容,但是我似乎找不到能正常工作的组合:

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

只能使用unittestpython 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)

对于碰巧来到这里的任何人,请注意。setup.py测试被认为是代码“气味”,并且也已设置为不推荐使用。 github.com/pytest-dev/pytest-runner/issues/50
Yashash Gaurav

Answers:


44

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

test_suite="tests",

1
我希望这样做更好,我遇到了这个问题:stackoverflow.com/questions/6164004/… “测试名称应与模块名称匹配。如果存在“ foo_test.py”测试,则需要有一个对应的模块foo.py 。”
查尔斯L.14年

1
我同意。就我而言,当我测试一个Python外部程序时,实际上没有带.py的Python模块,似乎没有实现这一目标的好方法。
Tom Swirly '16

2
这是正确的解决方案。我没有@CharlesL的问题。有。我所有的测试都命名为test_*.py。此外,我发现它实际上将在给定目录中递归搜索以找到任何扩展的类unittest.TestCast。如果您有一个目录结构,tests/first_batch/test_*.py并且其中有和,这将非常有用tests/second_batch/test_*.py。您只需指定test_suite="tests",,它将以递归的方式处理所有事情。请注意,每个嵌套目录都需要在其中有一个__init__.py文件。
dcmm88

39

使用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',
    ...
)

3
该解决方案存在一个问题,因为它创建了2个“级别”的单元测试。这意味着setuptools将创建一个“测试”命令,尝试从setup.my_test_suite创建一个TestSuite,这将强制其导入setup.py,这将再次运行setup()!第二次,它将创建一个新的(嵌套的)测试命令来运行所需的测试。对于大多数人来说,这可能并不明显,但是如果您尝试扩展测试命令(我需要对其进行修改,因为我无法“就地”运行测试),则可能会遇到奇怪的问题。使用stackoverflow.com/a/21726329/3272850代替
dcmm88

2
由于上述原因,这导致测试对我而言运行两次。通过将函数移至__init__.py测试文件夹的并对其进行引用来对其进行修复。
匿名

3
通过在脚本中的blocksetup()内部执行函数,可以轻松解决两次执行测试的问题。第一次执行安装脚本,因此将调用if块;第二次将安装脚本作为模块导入,因此不会调用if块。if __name__ == '__main__':setup.py
hoefling

嗯,我意识到我的setup.py根本不包含该test_suite参数,但是“ python setup.py test”仍然可以正常工作。这与文档所说的是不同的:“如果没有在setup()调用中设置test_suite,并且不提供--test-suite选项,则会发生错误。” 任何的想法?
RayLuo

21

您不需要进行配置即可工作。基本上有两种主要的实现方法:

快速方法

将您的重命名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

这是某人作为参考的样本


2
问题是如何使“ python setup.py测试”使用unittest的发现功能。这根本没有解决这个问题。
米克内罗尼(Mikenerone)2014年

嗯...是的,我完全以为这个问题提出了不同的要求。我不确定这是怎么发生的,我一定会失去理智的:(
反物质

5

一种可能的解决方案是简单地扩展testfordistutilssetuptools/的命令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)

3

另一个不太理想的解决方案,受到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)

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

顺便说一句,我在上面假设您只针对实际支持发现的Python版本(2.7和3.2+),因为问题专门针对此功能。当然,如果您也想保持与旧版本的兼容性,也可以将插入内容包装在版本检查中(在这种情况下,请使用setuptools的标准加载器)。
mikenerone 2014年

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命令是唯一的使用者,并且以指定的方式调用它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.