6

假设以下结构:

class SetupTestParam(object):
    def setup_method(self, method):
        self.foo = bar()

    @pytest.fixture
    def some_fixture():
        self.baz = 'foobar'

SetupTestParam用作测试类的父类。

class TestSomething(SetupTestParam):
    def test_a_lot(self, some_fixture):
        with self.baz as magic:
            with magic.fooz as more_magic:
                 blah = more_magic.much_more_magic() # repetative bleh
            ... # not repetative code here
            assert spam == 'something cool'

现在,编写测试变得重复(使用语句),我想编写一个装饰器来减少代码行数。但是 pytest 和函数签名存在问题。

我发现了应该有用的,但我无法让它工作。

classmethodSetupTestParam课堂上做了一个。

@classmethod
@decorator.decorator
def this_is_decorator(cls, f):
    def wrapper(self, *args, **kw):
        with self.baz as magic:
            with magic.fooz as more_magic:
                 blah = more_magic.much_more_magic() # repetative bleh
            return f(self, *args)
    return wrapper

装饰test_a_lot方法后,我收到错误TypeError: transaction_decorator() takes exactly 1 argument (2 given)

有人可以解释一下我在做什么错吗?(我假设测试方法有问题self?)

4

3 回答 3

3

链接装饰器并不是最简单的事情。一种解决方案可能是将两个装饰器分开。保持classmethod但移动decorator.decorator到最后:

@classmethod
def this_is_decorator(cls, f):
    def wrapper(self, *args, **kw):
        with self.baz as magic:
            with magic.fooz as more_magic:
                 blah = more_magic.much_more_magic() # repetative bleh
            return f(self, *args)
    return decorator.decorator(wrapper, f)

也许这对你有用。

于 2013-05-28T23:30:29.203 回答
2

经过一些调整并意识到我需要将参数传递给装饰器后,我选择将其编写为一个类:

class ThisIsDecorator(object):
    def __init__(self, param):
        self.param = param   # Parameter may vary with the function being decorated
    def __call__(self, fn):
        wraps(fn) # [1]
        def wrapper(fn, fn_self, *args): # [2] fn_self refers to original self param from function fn (test_a_lot) [2]
            with fn_self.baz as fn_self.magic: # I pass magic to fn_self to make magic accesible in function fn (test_a_lot)
                with fn_self.magic.fooz as more_magic:
                    blah = self.param.much_more_magic() # repetative bleh
            return fn(fn_self, *args)
        return decorator.decorator(wrapper, fn) 

[1] 我wraps曾经拥有原始fn __name__的 ,__module____doc__.

[2] 传递给 的参数wrapperself = <function test_a_lot at 0x24820c8> args = (<TestSomething object at 0x29c77d0>, None, None, None, None), kw = {}所以我拿出args[0]fn_self.

原始版本(不传递参数):

 @classmethod
 def this_is_decorator(cls, fn):
     @wraps(fn)
     def wrapper(fn, fn_self, *args):
         with fn_self.baz as fn_self.magic:
             with fn_self.magic.fooz as more_magic:
                 blah = more_magic.much_more_magic() # repetative bleh
             return fn(fn_self, *args)
     return decorator.decorator(wrapper,fn)

感谢 Mike Muller 指出正确的方向。

于 2013-06-03T06:46:54.443 回答
0

这是定义此方法时按时间顺序发生的情况。

  1. this_is_decorator被创建(未调用)。
  2. decorator.decorator(this_is_decorator)叫做。这将返回一个新函数,该函数变为this_is_decorator并具有相同的用法。
  3. classmethod(this_is_decorator)被调用,其结果是一个接受(cls, f)并返回的类方法wrapper
  4. 稍后在运行时,调用this_is_decorator将返回wrapper

但考虑到这this_is_decorator是一个类方法,我不清楚这就是你想要的。我猜你可能想要更多这样的东西:

from decorator import decorator
@decorator
def mydecorator(f):
  def wrapper(cls, *args, **kw):
    # ... logging, reporting, caching, whatever
    return f(*args, **kw)
  return wrapper

class MyClass(object):
  @classmethod
  @mydecorator
  def myclsmethod(a, b, c):
    # no cls or self value accepted here; this is a function not a method
    # ...

在这里,您的装饰器是在您的类之外定义的,因为它将普通函数更改为 a classmethod(并且因为您可能想在其他地方使用它)。这里的执行顺序是:

  1. mydecorator已定义,未调用。
  2. decorator(mydecorator)被调用,结果变为 mydecorator.
  3. 创作MyClass开始。
  4. myclsmethod被建造。它是一个普通的函数,而不是一个方法。VM 内部存在差异,因此您不必显式地为方法提供clsself参数。
  5. myclsmethod被传递给mydecorator(它本身已经被装饰过)并且结果(wrapper仍然是一个函数而不是一个方法
  6. 的结果mydecorator被传递给classmethod它返回绑定到的实际类方法MyClass.myclsmethod
  7. 完成的定义MyClass
  8. 稍后当MyClass.myclsmethod(a, b, c)被调用时,wrapper执行,然后调用原始myclsmethod(a, b, c)函数(它知道为f)而不提供cls参数。

由于您还需要准确地保留参数列表,因此即使参数的名称也保留在装饰函数中,除了额外的初始参数之外cls,您可以这样实现mydecorator

from decorator import decorator
from inspect import getargspec

@decorator
def mydecorator(func):
  result = [None]  # necessary so exec can "return" objects
  namespace = {'f': func, 'result': result}
  source = []
  add = lambda indent, line: source.append(' ' * indent + line)  # shorthand
  arglist = ', '.join(getargspec(func).args)  # this does not cover keyword or default args
  add(0, 'def wrapper(cls, %s):' % (arglist,))
  add(2, 'return f(%s)' % (arglist,))
  add(0, 'result[0] = wrapper')  # this is how to "return" something from exec
  exec '\n'.join(source) in namespace
  return result[0]  # this is wrapper

这有点难看,但这是我知道的根据数据动态设置函数参数列表的唯一方法。如果返回 alambda没问题,您可以使用eval而不是exec,这消除了对写入的数组的需要,但其他方面大致相同。

于 2013-05-28T15:12:41.933 回答