为pytest测试方法编写装饰器

时间:2013-05-28 14:14:57

标签: python decorator pytest

假设以下结构:

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和函数签名存在问题。

我发现library应该会有所帮助,但我无法让它发挥作用。

我在classmethod课程中提出了SetupTestParam

@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存在问题?)

3 个答案:

答案 0 :(得分:2)

链接装饰器并不是最简单的事情。一种解决方案可能是分离两个装饰器。保持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)

也许这适合你。

答案 1 :(得分: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
  5. 但考虑到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
        # ...
    

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

    1. mydecorator已定义,未调用。
    2. decorator(mydecorator)被调用,结果变为 mydecorator
    3. 创建MyClass开始。
    4. myclsmethod已创建。这是一个普通的功能,而不是一种方法。 VM中存在差异,因此您无需为方法明确提供clsself个参数。
    5. myclsmethod传递给mydecorator(之前已经过装饰),结果(wrapper仍然是函数而不是方法。< / LI>
    6. mydecorator的结果传递给classmethod,后者返回绑定到MyClass.myclsmethod的实际类方法。
    7. MyClass完成的定义。
    8. 稍后调用MyClass.myclsmethod(a, b, c)时,wrapper会执行,然后调用原始myclsmethod(a, b, c)函数(它知道为f)而不提供cls参数。
    9. 由于你还需要精确地保存参数列表,所以即使参数的名称保存在装饰函数中,除了有一个额外的初始参数cls,你就可以实现{{1这样:

      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 是正常的,则可以使用lambda而不是eval,这样就无需使用可写入的数组,但在其他方面也是如此。

答案 2 :(得分:0)

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

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]传递给wrapper的参数为self = <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指出正确的方向。