9

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

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 或很多的情况,也许?)
  • 在目录之前传入模块:目录必须放在第一位,否则你会得到重复的测试。

这种脆弱性是荒谬的,如果你有改进它的想法,我欢迎评论,或者我建立了 一个 github repo,我的实验试图让它发挥作用

除此之外,以下工作,包括拾取安装到站点包中的多个项目:

#!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

4 回答 4

4

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

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

对于您的生产代码,我只需调用nose nosetests -a safe,或NOSE_ATTR=safe在您的操作系统生产测试环境中设置,或调用nose 对象上的run 方法以使用-a基于您的命令行选项在python 中本地运行它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属性 ( from nose.tools import istest)将它们显式标记为测试

于 2013-10-23T04:34:51.200 回答
1

事实证明这是一团糟:Nose 几乎完全使用该 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)
于 2013-12-30T22:22:02.343 回答
0

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

至于文档,您可以尝试从这里开始。

如果您不喜欢默认的 pytest 测试收集方法,可以使用此处的说明对其进行自定义。

于 2013-10-22T16:29:30.610 回答
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.TestSuiteand相对unittest.TestCase)。我以这种方式保持类的命名约定MyFile,因为它旨在表示您从文件中读取的内容,但当然您基本上可以将其解耦(就像我在这里所做的那样)。请参阅此处以获取官方示例。唯一的限制是,按照我写的方式,它foo必须作为文件存在,但您也可以将其解耦,例如通过使用conftest.py或树中存在的任何其他文件名(并且只有一次,否则每个文件都会运行匹配 - 如果您不对if path.basename树中存在的每个文件进行测试!!!)

您可以从命令行运行它

py.test -whatever -options

或以编程方式从您使用的任何代码中

import pytest
pytest.main("-whatever -options")

py.test 的好处是您可以解锁许多非常强大的插件,例如html 报告

于 2016-05-24T22:38:16.240 回答