Autospec“通过”装饰器与unittest.mock

时间:2014-06-04 16:15:14

标签: python unit-testing mocking python-unittest

假设我有一个简单的装饰方法,如下所示:

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

装饰器“擦除”用于自动指定目的的方法签名:

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

这应该提出:

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

由于关键字参数中的拼写错误,我遇到了类似这样的错误。

有没有办法编写装饰器(或给autospec提供“提示”)以便捕获这些类型的错误?

3 个答案:

答案 0 :(得分:1)

我不认为autospec可以直接执行此操作。不过,你可以在装饰器中做一些小装饰,以便测试你的未修饰功能。如果您让装饰者保存对未修饰函数的引用:

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

您可以通过模拟的装饰功能访问它:

>>> 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'>

但是,如果您自动指定整个实例,则这不起作用。不知道为什么。

>>> 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'>

另外值得注意的是Venusian,它可以改变装饰器绑定到装饰方法的方式,专门用于解决此用例。不过,可能比你想要的更重量级。

答案 1 :(得分:0)

一位同事指着我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实现了正确的魔法,不会隐藏@my_decorator背后的包装函数的签名。

答案 2 :(得分:0)

你可以使用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)

如果您将代码置于文件中并运行,则会在最后一行中看到跟踪:

TypeError: too many positional arguments

没有@wraps脚本以退出代码0结束。