【问题标题】:Issue mocking with a decorated method call in Python 2.7在 Python 2.7 中使用装饰方法调用进行模拟
【发布时间】:2016-04-20 10:37:31
【问题描述】:

这段代码:

import mock
from functools import wraps

def dec(f):
    @wraps(f)
    def f_2(*args, **kwargs):
        pass
    return f_2


class Example(object):
    def __init__(self):
        pass

    @dec
    def method_1(self, arg):
        pass

    def method_2(self, arg):
        self.method_1(arg)



def test_example():
    m = mock.create_autospec(Example)
    Example.method_2(m, "hello")
    m.method_1.assert_called_once_with("hello")

使用 py.test 产生此错误

    def test_example():
        m = mock.create_autospec(Example)
>       Example.method_2(m, "hello")

example.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
example.py:20: in method_2
    self.method_1(arg)
anaconda2/envs/arctic/lib/python2.7/site-packages/mock/mock.py:1061: in __call__
    _mock_self._mock_check_sig(*args, **kwargs)
anaconda2/envs/arctic/lib/python2.7/site-packages/mock/mock.py:227: in checksig
    sig.bind(*args, **kwargs)
anaconda2/envs/arctic/lib/python2.7/site-packages/mock/mock.py:95: in fixedbind
    return self._bind(args, kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <funcsigs.Signature object at 0x7f09b8685e10>, args = ('hello',), kwargs = {}, partial = False

    def _bind(self, args, kwargs, partial=False):
        '''Private method.  Don't use directly.'''
        arguments = OrderedDict()

        parameters = iter(self.parameters.values())
        parameters_ex = ()
        arg_vals = iter(args)

        if partial:
            # Support for binding arguments to 'functools.partial' objects.
            # See 'functools.partial' case in 'signature()' implementation
            # for details.
            for param_name, param in self.parameters.items():
                if (param._partial_kwarg and param_name not in kwargs):
                    # Simulating 'functools.partial' behavior
                    kwargs[param_name] = param.default

        while True:
            # Let's iterate through the positional arguments and corresponding
            # parameters
            try:
                arg_val = next(arg_vals)
            except StopIteration:
                # No more positional arguments
                try:
                    param = next(parameters)
                except StopIteration:
                    # No more parameters. That's it. Just need to check that
                    # we have no `kwargs` after this while loop
                    break
                else:
                    if param.kind == _VAR_POSITIONAL:
                        # That's OK, just empty *args.  Let's start parsing
                        # kwargs
                        break
                    elif param.name in kwargs:
                        if param.kind == _POSITIONAL_ONLY:
                            msg = '{arg!r} parameter is positional only, ' \
                                  'but was passed as a keyword'
                            msg = msg.format(arg=param.name)
                            raise TypeError(msg)
                        parameters_ex = (param,)
                        break
                    elif (param.kind == _VAR_KEYWORD or
                                                param.default is not _empty):
                        # That's fine too - we have a default value for this
                        # parameter.  So, lets start parsing `kwargs`, starting
                        # with the current parameter
                        parameters_ex = (param,)
                        break
                    else:
                        if partial:
                            parameters_ex = (param,)
                            break
                        else:
                            msg = '{arg!r} parameter lacking default value'
                            msg = msg.format(arg=param.name)
                            raise TypeError(msg)
            else:
                # We have a positional argument to process
                try:
                    param = next(parameters)
                except StopIteration:
                    raise TypeError('too many positional arguments')
                else:
                    if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
                        # Looks like we have no parameter for this positional
                        # argument
>                       raise TypeError('too many positional arguments')
E                       TypeError: too many positional arguments

anaconda2/envs/arctic/lib/python2.7/site-packages/funcsigs/__init__.py:716: TypeError

有什么方法可以测试这段代码的意图吗?如果您删除装饰器,或者如果您将参数设置为 method_1 关键字 args,它会神奇地起作用。我不确定该错误是在 Mock 本身还是在 funcsigs 中,但显然有些不对劲。有解决方法吗?另一种测试 method_2 调用 method_1 的方法?

我应该提到这是模拟 1.3.0。由于某种原因,此代码在 1.0.1 中有效,但我需要使用最新版本的 mock。

更新

这行得通:

def test_example():
    m = mock.Mock(spec=Example)
    m.method_1 = mock.Mock()
    Example.method_2(m, "hello")
    m.method_1.assert_called_once_with("hello")

【问题讨论】:

  • test_example 包裹到 ExampleTest class 值得一试

标签: python python-2.7 unit-testing mocking pytest


【解决方案1】:

从 mock 1.1.0 开始,框架开始深入检查方法签名。看看changelog@在

问题 #17015:当它有一个规范时,一个 Mock 对象现在在匹配调用时检查它的签名,以便可以按位置或按名称匹配参数

现在,如果您将 f2() 签名更改为 def f_2(arg, *args, **kwargs),它应该可以工作。

也许这是一个新的错误,签名检查无法正确理解调用是正确的(至少兼容)。我认为您可以提交错误并在此处发布票证链接。

作为解决方法,您可以通过添加从 method_1 中删除自动指定

m.method_1 = mock.Mock()

【讨论】:

  • 这会起作用,但除非我遗漏了什么,否则我需要实际实例化该对象。即我必须做类似的事情: m = Example() m.method_1 = mock.Mock() 等。假设我不想调用 init 因为在不平凡的例子中这是昂贵的并且需要设置很多其他的东西。这就是使用 autospec 的最大“胜利”。
猜你喜欢
  • 2017-05-25
  • 2016-08-17
  • 2016-01-06
  • 2014-05-31
  • 2013-07-19
  • 1970-01-01
  • 1970-01-01
  • 2014-08-16
相关资源
最近更新 更多