如何让nose发现动态生成的测试用例?

6 投票
3 回答
2541 浏览
提问于 2025-04-11 20:33

这是我之前一个问题的后续内容,之前的问题可以在这里找到。

在之前的问题中,我们探讨了如何对一组相似的函数进行测试,确保测试不会因为第一个失败的函数就停止。

我比较喜欢的解决方案是使用元类动态地将测试插入到一个unittest.TestCase中。不过,遗憾的是,nose工具无法识别这种方式,因为它是静态扫描测试用例的。

那么,我该如何让nose发现并运行这样的TestCase呢?请参考这里的示例,了解具体的TestCase。

3 个回答

1

你可以试着用type()来生成测试用例的类。

class UnderTest_MixIn(object):

    def f1(self, i):
        return i + 1

    def f2(self, i):
        return i + 2

SomeDynamicTestcase = type(
    "SomeDynamicTestcase", 
    (UnderTest_MixIn, unittest.TestCase), 
    {"even_more_dynamic":"attributes .."}
)

# or even:

name = 'SomeDynamicTestcase'
globals()[name] = type(
    name, 
    (UnderTest_MixIn, unittest.TestCase), 
    {"even_more_dynamic":"attributes .."}
)

这个类应该在nose尝试导入你的测试模块时创建,所以应该没问题。

这种方法的好处是,你可以动态地创建很多不同组合的测试。

2

Nose这个工具不会静态地扫描测试,所以你可以用一些特别的技巧来让Nose找到你的测试。

难点在于,普通的技巧并不能正确设置一个叫做func_name的属性,而Nose正是通过这个属性来判断你类里的方法是否是测试。

下面是一个简单的元类。它会查看函数字典,并为每个找到的方法添加一个新方法,确保这个方法有文档字符串。这些新方法的名字会被命名为"test_%d" %i

import new
from inspect import isfunction, getdoc

class Meta(type):
    def __new__(cls, name, bases, dct):

        newdct = dct.copy()
        for i, (k, v) in enumerate(filter(lambda e: isfunction(e[1]), dct.items())):
            def m(self, func):
                assert getdoc(func) is not None

            fname = 'test_%d' % i
            newdct[fname] = new.function(m.func_code, globals(), fname,
                (v,), m.func_closure)

        return super(Meta, cls).__new__(cls, 'Test_'+name, bases, newdct)

现在,我们来创建一个使用这个元类的新类。

class Foo(object):
    __metaclass__ = Meta

    def greeter(self):
        "sdf"
        print 'Hello World'

    def greeter_no_docstring(self):
        pass

在运行时,Foo实际上会被命名为Test_Foo,并且会有greetergreeter_no_docstringtest_1test_2这些方法。当我在这个文件上运行nosetests时,输出结果是:

$ nosetests -v test.py
test.Test_Foo.test_0 ... FAIL
test.Test_Foo.test_1 ... ok

======================================================================
FAIL: test.Test_Foo.test_0
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Library/Frameworks/EPD64.framework/Versions/7.3/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/Users/rmcgibbo/Desktop/test.py", line 10, in m
    assert getdoc(func) is not None
AssertionError

----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (failures=1)

这个元类本身并不是特别有用,但如果你把Meta用作一种功能性元类(也就是说,把一个类作为参数传入,然后返回一个新类,这个新类的名字改了,以便Nose能找到它),那它就有用了。我用这种方法来自动测试文档字符串是否符合Numpy标准,作为Nose测试套件的一部分。

另外,我在使用new.function时遇到了很多麻烦,这就是为什么这段代码使用m(self, func),其中func被设为默认参数。用value来做闭包会更自然,但似乎不太管用。

8

Nose有一个叫“测试生成器”的功能,可以用来处理这种情况。你需要写一个生成器函数,这个函数会逐个返回你想要运行的“测试用例”函数,以及它的参数。根据你之前的例子,这样可以在不同的测试中检查每一个函数:

import unittest
import numpy

from somewhere import the_functions

def test_matrix_functions():
    for function in the_functions:
        yield check_matrix_function, function

def check_matrix_function(function)
    matrix1 = numpy.ones((5,10))
    matrix2 = numpy.identity(5)
    output = function(matrix1, matrix2)
    assert matrix1.shape == output.shape, \
           "%s produces output of the wrong shape" % str(function)

撰写回答