简体   繁体   English

使用unittest.mock通过装饰器自动指定

[英]Autospec “through” a decorator with unittest.mock

Suppose I have a simple decorated method like the following: 假设我有一个简单的装饰方法,如下所示:

def my_decorator(fn):
  def _wrapper(*args, **kwargs):
    print 'Calling decorated function'
    fn(*args, **kwargs)
  return _wrapper

class Foo(object):
  @my_decorator
  def incr(self, x):
    return x+1

The decorator "erases" the method signature for autospec'ing purposes: 装饰器为自动指定目的“擦除”方法签名:

>>> mock_foo = mock.create_autospec(Foo, instance=True)
>>> mock_foo.incr(1, 2, 3, 4)
<MagicMock name='mock.incr()' id='23032592'>

This should raise: 这应该引起:

TypeError: <lambda>() takes exactly 2 arguments (5 given)

I've had bugs like this creep through due to typos in keyword arguments. 由于关键字参数中的拼写错误,我遇到了类似这样的错误。

Is there any way to write the decorator (or give a "hint" to autospec) so that these kinds of errors will be caught? 有什么方法可以编写装饰器(或给“提示”进行自动指定),以便捕获此类错误?

I don't think autospec can do this directly. 我认为autospec不能直接做到这一点。 You can do a little hackery in the decorator to make it possible to test your undecorated function, though. 不过,您可以在装饰器中做一些黑客操作,以测试未修饰的功能。 If you make your decorator save a reference to the undecorated function: 如果让装饰器保存对未修饰函数的引用:

def my_decorator(fn):
  def _wrapper(*args, **kwargs):
    print 'Calling decorated function'
    fn(*args, **kwargs)
  _wrapper._orig = fn
  return _wrapper

You can access it via the mocked decorated function: 您可以通过模拟的装饰函数访问它:

>>> mock_incr = mock.create_autospec(Foo.incr)
>>> mock_incr(1,3,4,5,5)               # Decorated function doesn't fail.
<MagicMock name='mock()' id='8734864'>
>>> mock_incr._orig(1,3,4,5,5)         # But the original does, which is what we want
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib64/python2.6/site-packages/mock.py", line 954, in __call__
    _mock_self._mock_check_sig(*args, **kwargs)
TypeError: <lambda>() takes exactly 3 arguments (6 given)
>>> mock_incr._orig(1,3)
<MagicMock name='mock._orig()' id='8739664'>

This doesn't work if you autospec the whole instance, though. 但是,如果您自动指定整个实例,则此方法不起作用。 Not sure why. 不知道为什么。

>>> mock_foo = mock.create_autospec(Foo, instance=True)
>>> mock_foo.incr(1,3,4,5)             # We expect this to not raise an exception
<MagicMock name='mock.incr2()' id='8758416'>
>>> mock_foo.incr._orig(1,3,4,5)       # But we were hoping this would :(
<MagicMock name='mock.incr._orig()' id='8740624'>

Also worth noting is Venusian , which can change the way decorators get bound to decorated methods specifically to address this use-case. 还值得注意的是Venusian ,它可以更改装饰器绑定到装饰方法的方式,专门解决此用例。 Might be more heavyweight than you want, though. 但是,可能会比您想要的更重。

A coworker pointed me to the decorator library 一位同事将我指向装饰器库

from decorator import decorator

@decorator
def my_decorator(fn, *args, **kwargs):
  print 'Calling decorated function'
  return fn(*args, **kwargs)

class Foo(object):
  @my_decorator
  def incr(self, x):
    return x+1

@decorator implements the right magic to not hide the wrapped function's signature behind @my_decorator. @decorator实现了正确的魔术,不会将包装的函数的签名隐藏在@my_decorator后面。

You can use functools.wraps, mock.create_autospec understands it: 您可以使用functools.wraps,mock.create_autospec可以理解:

from functools import wraps
from unittest import mock


def my_decorator(fn):
   @wraps(fn)
   def _wrapper(*args, **kwargs):
       print('Calling decorated function')
       fn(*args, **kwargs)
   return _wrapper


class Foo(object):
    @my_decorator
    def incr(self, x):
        return x + 1


if __name__ == '__main__':
    mock_foo = mock.create_autospec(Foo, instance=True)
    mock_foo.incr(1, 2, 3, 4)

if you place code above into file and run, you'll see trace with last line: 如果将上面的代码放入文件中并运行,您将在最后一行看到跟踪:

TypeError: too many positional arguments

Without @wraps script ends with exit code 0. 如果不使用@wraps,则脚本以退出代码0结尾。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM