在nose或pytest中收集以编程方式生成的测试套件的好方法

时间:2013-10-22 16:21:57

标签: python nose pytest

说我有一个这样的测试套件:

class SafeTests(unittest.TestCase):
    # snip 20 test functions

class BombTests(unittest.TestCase):
    # snip 10 different test cases

我目前正在做以下事情:

suite = unittest.TestSuite()
loader = unittest.TestLoader()
safetests = loader.loadTestsFromTestCase(SafeTests)
suite.addTests(safetests)

if TARGET != 'prod':
    unsafetests = loader.loadTestsFromTestCase(BombTests)
    suite.addTests(unsafetests)


unittest.TextTestRunner().run(suite)

我有一个重大问题,一个有趣的观点

  • 我想使用nose或py.test(真的不重要)
  • 我有大量不同的应用程序正在公开这些测试 套房通过入口点。

    我希望能够在所有已安装的内容中聚合这些自定义测试 应用程序,所以我不能只是使用一个聪明的命名约定。我不 特别关心这些通过入口点暴露,但我 关心能否跨应用程序运行测试 站点包。 (不仅仅是导入......每个模块。)

关心维护当前的依赖关系 unittest.TestCase,诋毁这种依赖性实际上是一个目标。


编辑这是为了确认@ Oleksiy关于将args传递给的观点 事实上,nose.run确实可以解决一些问题。

工作的事情:

  • 传递所有想要执行的文件(怪异的
  • 传递想要执行的所有模块。 (这要么执行 没什么,错的,或者太多的东西。有趣的情况为0,1或 很多,也许?)
  • 在目录之前传递模块:目录必须到来 首先,否则你会得到重复的测试。

这种脆弱是荒谬的,如果你有改进它的想法我欢迎 评论,或我设置 a github repo with my experiments trying to get this to work

除此之外,以下工作,包括拾取多个项目 安装到site-packages中:

#!python
import importlib, os, sys
import nose

def runtests():
    modnames = []
    dirs = set()
    for modname in sys.argv[1:]:
        modnames.append(modname)

        mod = importlib.import_module(modname)
        fname = mod.__file__
        dirs.add(os.path.dirname(fname))

    modnames = list(dirs) + modnames

    nose.run(argv=modnames)

if __name__ == '__main__':
    runtests()

如果保存到runtests.py文件中,则在运行时执行正确的操作:

runtests.py project.tests otherproject.tests

4 个答案:

答案 0 :(得分:4)

对于鼻子,您可以使用attribute插件进行两个测试并选择运行哪个测试,这对于选择要运行的测试非常有用。我会保留两个测试并为它们分配属性:

from nose.plugins.attrib import attr

@attr("safe")
class SafeTests(unittest.TestCase):
    # snip 20 test functions

class BombTests(unittest.TestCase):
    # snip 10 different test cases

对于你的生产代码,我只需要用nosetests -a safe调用nose,或者在你的os生产测试环境中设置NOSE_ATTR=safe,或者在nose对象上调用run方法在python中用{{1}本地运行它基于-a

的命令行选项
TARGET

最后,如果由于某种原因未发现您的测试,您可以使用import sys import nose if __name__ == '__main__': module_name = sys.modules[__name__].__file__ argv = [sys.argv[0], module_name] if TARGET == 'prod': argv.append('-a slow') result = nose.run(argv=argv) 属性(@istest)明确标记为测试

答案 1 :(得分:1)

这结果是一团糟:鼻子几乎完全使用了 TestLoader.load_tests_from_names函数(它是唯一函数在其中测试过的 unit_tests/test_loader) 因为我想从任意python对象实际加载东西我 似乎需要自己编写一个使用什么样的加载函数。

然后,另外,要像nosetests脚本一样正确地工作 我需要导入大量的东西。我完全不确定这一点 是最好的做事方式,甚至不是。但这是一个被剥夺了 对我有用的例子(没有错误检查,不那么详细):

import sys
import types
import unittest

from nose.config import Config, all_config_files
from nose.core import run
from nose.loader import TestLoader
from nose.suite import ContextSuite
from nose.plugins.manager import PluginManager

from myapp import find_test_objects

def load_tests(config, obj):
    """Load tests from an object

    Requires an already configured nose.config.Config object.

    Returns a nose.suite.ContextSuite so that nose can actually give
    formatted output.
    """

    loader = TestLoader()
    kinds = [
        (unittest.TestCase, loader.loadTestsFromTestCase),
        (types.ModuleType, loader.loadTestsFromModule),
        (object, loader.loadTestsFromTestClass),
    ]
    tests = None
    for kind, load in kinds.items():
        if isinstance(obj, kind) or issubclass(obj, kind):
            log.debug("found tests for %s as %s", obj, kind)
            tests = load(obj)
            break

    suite = ContextSuite(tests=tests, context=obj, config=config)

def main():
    "Actually configure the nose config object and run the tests"
    config = Config(files=all_config_files(), plugins=PluginManager())
    config.configure(argv=sys.argv)

    tests = []
    for group in find_test_objects():
        tests.append(load_tests(config, group))

    run(suite=tests)

答案 2 :(得分:0)

如果你的问题是,“如何让pytest'看'测试?',你需要在每个测试文件和每个测试用例(即函数)之前加上'test_'。然后,只需在pytest命令行上传递要搜索的目录,它将递归搜索匹配'test_XXX.py'的文件,从中收集'test_XXX'函数并运行它们。

至于文档,您可以尝试启动here.

如果您不喜欢默认的pytest测试集合方法,可以使用方向here.

对其进行自定义

答案 3 :(得分:0)

如果您愿意更改代码以生成py.test"套件" (我的定义)而不是单元测试套件(技术术语),您可以轻松地这样做。创建一个名为conftest.py的文件,如下面的存根

import pytest

def pytest_collect_file(parent, path):
    if path.basename == "foo":
        return MyFile(path, parent)

class MyFile(pytest.File):
    def collect(self):
        myname="foo"
        yield MyItem(myname, self)
        yield MyItem(myname, self)

class MyItem(pytest.Item):
    SUCCEEDED=False
    def __init__(self, name, parent):
        super(MyItem, self).__init__(name, parent)

    def runtest(self):
        if not MyItem.SUCCEEDED:
            MyItem.SUCCEEDED = True
            print "good job, buddy"
            return
        else:
            print "you sucker, buddy"
            raise Exception()

    def repr_failure(self, excinfo):
        return ""

您将在MyFileMyItem类中生成/添加代码(而不是unittest.TestSuiteunittest.TestCase)。我保持MyFile类的命名约定,因为它旨在表示您从文件中读取的内容,但当然您基本上可以将其解耦(正如我在此处所做的那样)。有关其正式示例,请参阅here。唯一的限制是,在我写的方式中,foo必须作为文件存在,但您也可以将其解耦,例如:通过使用conftest.py或树中存在的任何其他文件名(并且只有一次,否则所有匹配的文件都会运行 - 如果您不为每个文件执行if path.basename测试树中存在的文件!!!)

您可以使用

从命令行运行此命令
py.test -whatever -options

或使用

的任何代码编程
import pytest
pytest.main("-whatever -options")

py.test的优点在于你可以解锁许多非常强大的插件,例如html report