我正在使用nose test generators功能来运行具有不同上下文的相同测试。因为每次测试需要以下锅炉板:
class TestSample(TestBase):
def test_sample(self):
for context in contexts:
yield self.check_sample, context
def check_sample(self, context):
"""The real test logic is implemented here"""
pass
我决定编写以下装饰器:
def with_contexts(contexts=None):
if contexts is None:
contexts = ['twitter', 'linkedin', 'facebook']
def decorator(f):
@wraps(f)
def wrapper(self, *args, **kwargs):
for context in contexts:
yield f, self, context # The line which causes the error
return wrapper
return decorator
装饰器按以下方式使用:
class TestSample(TestBase):
@with_contexts()
def test_sample(self, context):
"""The real test logic is implemented here"""
var1 = self.some_valid_attribute
执行测试时,会抛出错误,指定正在访问的属性不可用。但是,如果我将调用该方法的行更改为以下方法,则可以正常工作:
yield getattr(self, f.__name__), service
我知道上面的代码片段会创建一个绑定方法,就像第一个 self 手动传递给函数一样。但是,就我的理解而言,第一个片段应该也可以正常工作。如果有人能澄清这个问题,我将不胜感激。
问题的标题通常与装饰器中的实例方法相关,但我保留了特定于我的上下文的描述。
答案 0 :(得分:3)
您可以使用functools.partial
将包装的函数绑定到self
,就像方法一样:
from functools import partial
def decorator(f):
@wraps(f)
def wrapper(self, *args, **kwargs):
for context in contexts:
yield partial(f, self), context
return wrapper
现在你正在屈服于偏见,当被称为yieldedvalue(context)
时,会调用f(self, context)
。
答案 1 :(得分:0)
据我所知,有些事情并不合适。首先,你的装饰师就像
def with_contexts(contexts=None):
if contexts is None:
contexts = ['twitter', 'linkedin', 'facebook']
def decorator(f):
@wraps(f)
def wrapper(self, *args, **kwargs):
for context in contexts:
yield f, self, context # The line which causes the error
return wrapper
return decorator
但你像
一样使用它@with_contexts
def test_sample(self, context):
"""The real test logic is implemented here"""
var1 = self.some_valid_attribute
这是错误的:这会调用with_context(test_sample)
,但您需要with_context()(test_sample)
。
@with_contexts()
def test_sample(self, context):
"""The real test logic is implemented here"""
var1 = self.some_valid_attribute
即使您没有提供contexts
参数。
其次,你装饰了错误的函数:你的用法表明test
函数为每个上下文产生check
函数。您要包装的函数执行检查功能的工作,但您必须在测试功能之后命名它。
可以使用self
将partial
应用于某个方法,就像Martijn写的那样,但它也可以像Python在幕后所做的那样完成:
method.__get__(self, None)
或者更好
method.__get__(self, type(self))
你可以达到同样的目的。 (也许你的原始版本也可以工作,产生要调用的函数和使用的参数。我不清楚这是它的工作方式。)