pytest发现错过了装饰方法

时间:2015-11-25 18:47:59

标签: python pytest python-decorators

我希望能够以类似于以下的格式编写一堆测试:

class TestPytest:
    @given(3)
    @expect(3)
    def test_passes(self, g, e):
        assert g == e

    @given(3)
    @expect(4)
    def test_fails(self, g, e):
        assert g == e

    def test_boring(self): # for comparison
        pass

(我不相信这是一个好主意,但我会在更进一步的方向上采取它,所以它并不像看起来那么奇怪。)

为此,我试图编写这些装饰器:

import functools

class WrappedTest(object):
    def __init__(self, f):
        self.func = f
        self.given = []
        self.expects = []

    def __get__(self, instance, owner):
        @functools.wraps(self.func)
        def call(*a, **kw):
            return self.func(instance, self.given, self.expects,
                             *a, **kw)
        return call

def given(*objects):
    def wrapped(test):
        if not isinstance(test, WrappedTest):
            test_tmp = WrappedTest(test)
            test = functools.update_wrapper(test_tmp, test)

        test.given.extend(objects)
        return test
    return wrapped

def expect(*objects):
    def wrapped(test):
        if not isinstance(test, WrappedTest):
            test_tmp = WrappedTest(test)
            test = functools.update_wrapper(test_tmp, test)

        test.expects.extend(objects)
        return test
    return wrapped

但是当我尝试运行此测试时,pytest找不到test_passestest_fails。它找到了test_boring

我的工作假设是我没有正确包装测试方法。它们显示为函数而不是方法:

>>> test_pytest.TestPytest().test_fails
<function test_pytest.test_fails>

>>> test_pytest.TestPytest().test_boring
<bound method TestPytest.test_boring of <test_pytest.TestPytest instance at 0x101f3dab8>>

但我不确定如何解决这个问题。我已经尝试将functools.wraps(self.func)更改为functools.wraps(self.func.__get__(instance, owner)),理论上它将使用绑定方法而不是函数进行包装。但这是一种猜测,它没有用。

我知道pytest能够找到正确编写的装饰函数,所以大概我做错了什么,但我不确定是什么。

1 个答案:

答案 0 :(得分:1)

看起来我错了包装。通过pytest源,它以不同于方法的方式处理嵌套类。它通过__dict__访问会员,忽略__get__,因此WrappedTest并未成功假装成方法。

我已经用一个函数替换了WrappedTest实例,它似乎工作正常(即使没有@functools.wraps行):

import functools
from collections import namedtuple

def wrap_test_method(meth):
    if hasattr(meth, '_storage'):
        return meth

    Storage = namedtuple('Storage', ['given', 'expects'])
    sto = Storage(given=[], expects=[])

    @functools.wraps(meth)
    def new_meth(self, *a, **kw):
        return meth(self, sto.given, sto.expects, *a, **kw)
    new_meth._storage = sto

    return new_meth

def given(*objects):
    def decorator(test_method):
        new_test_method = wrap_test_method(test_method)
        new_test_method._storage.given.extend(objects)
        return new_test_method

    return decorator

def expect(*objects):
    def decorator(test_method):
        new_test_method = wrap_test_method(test_method)
        new_test_method._storage.expects.extend(objects)
        return new_test_method

    return decorator