pytest 发现遗漏了装饰方法
pytest discovery misses decorated methods
我希望能够以类似于这样的格式编写一堆测试:
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_passes
或 test_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 能够找到正确编写的修饰函数,所以大概我做错了什么,但我不确定是什么。
看来我关于包装的做法是错误的。查看 pytest 源代码,它对待嵌套 类 的方式与方法不同。它通过忽略 __get__
的 __dict__
访问成员,因此 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
我希望能够以类似于这样的格式编写一堆测试:
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_passes
或 test_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 能够找到正确编写的修饰函数,所以大概我做错了什么,但我不确定是什么。
看来我关于包装的做法是错误的。查看 pytest 源代码,它对待嵌套 类 的方式与方法不同。它通过忽略 __get__
的 __dict__
访问成员,因此 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