【问题标题】:Writing decorator for pytest test method为 pytest 测试方法编写装饰器
【发布时间】:2013-05-28 14:14:57
【问题描述】:

假设如下结构:

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 应该会有所帮助,但我无法让它发挥作用。

我在SetupTestParam 班级中创建了一个classmethod。

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

【问题讨论】:

    标签: python decorator pytest


    【解决方案1】:

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

    也许这对你有用。

    【讨论】:

    • 我如何从包装函数中的 SetupTestParam 访问 self.foo?
    • 是的。我也想办法休息了。以下是 pytest 中显示的包装器参数:self = <function test_a_lot at 0x24820c8> args = (<TestSomething object at 0x29c77d0>, None, None, None, None), kw = {} 所以现在不是self.baz 而是args[0].baz。我只需要返回f(*args)。
    • 太棒了。也许您可以回答自己的问题,将答案中的所有部分和 cmets 放在一起。鼓励自己回答问题:meta.stackexchange.com/questions/2706/…
    • 是的,我会在完成装饰器调整后立即执行此操作。使用args[0] 看起来很糟糕。有什么方法可以将函数的实例(在这种情况下为 self 参数)传递给 wrapper ?我希望看到wrapper 的self 成为test_a_lot 的self 的参考。
    【解决方案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] 传递给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 指出正确的方向。

    【讨论】:

    • 如果迈克的回答有帮助,通常很高兴支持它... :)
    【解决方案3】:

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

    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
        # ...
    

    这里你的装饰器是在你的类之外定义的,因为它把一个普通的函数变成了一个classmethod(因为你可能想在其他地方使用它)。这里的执行顺序是:

    1. mydecorator 已定义,未调用。
    2. decorator(mydecorator) 被调用,结果变成 mydecorator。
    3. 开始创建MyClass。
    4. myclsmethod 已创建。它是一个普通的函数,而不是一个方法。 VM 内部存在差异,因此您不必为方法显式提供 cls 或 self 参数。
    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
    

    这有点难看,但这是我知道的根据数据动态设置函数参数列表的唯一方法。如果返回 lambda 没问题,您可以使用 eval 代替 exec,这样就无需写入数组,但其他方面基本相同。

    【讨论】:

    • 我使用classmethod 来装饰this_is_decorator,因为我想在继承的class TestSomething 中使用这个装饰器。我之所以使用decorator.decorator 是因为 pytest 夹具,我需要装饰器调用具有正确签名的函数。基本上,pytest 必须看到 f(some_fixture) 而不是 f(*args) 因为 *args 不是任何固定装置。
    • 让我看看我是否理解正确。您希望一个或多个装饰器协作 (1) 生成一个类方法 (2) 装饰一个不接受 cls 参数的函数 (3) 装饰一个可以分析其参数列表的函数,如 inspect.getargspec 和不会只接受可变参数。
    • 如果一切正确,我认为最好的办法是使用getargspec 获取参数列表,将wrapper 的源构建为字符串(带有额外的初始cls 参数),使用exec 语句创建wrapper 函数,并返回它。所有这些都将在您的装饰器中完成。
    • 也许你是对的,但我无法想象,你能根据我提供的例子给我看一个简单的例子吗?或者只是将其编辑为您的答案。
    猜你喜欢
    • 2019-08-30
    • 2014-09-20
    • 1970-01-01
    • 2015-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-26
    • 1970-01-01
    相关资源
    最近更新 更多