检查 unittest.mock 不可知论地调用参数 w.r.t。它们是作为位置参数还是关键字参数传递

check unittest.mock call arguments agnostically w.r.t. whether they have been passed as positional arguments or keyword arguments

unittest.mock.Mock 对象被调用时,我可以使用调用的确切签名检查参数值:

from unittest.mock import Mock

m = Mock()  # creation of mock
m('foo', bar='baz')  # call to the mock
m.assert_called_once_with('foo', bar='baz')  # check call arguments

检查具有相同值的不同签名将失败。例如,如果我们检查 'baz' 作为位置参数而不是命名参数,断言将失败:

m.assert_called_once_with('foo', 'baz')
# AssertionError: Expected call: mock('foo', 'baz')
# Actual call: mock('foo', bar='baz')

必须的。如果被 m 替换的函数是

def actual_fu(foo, bar):
    # do something

那么调用将是等效的,但如果是

def a_different_actual_fu(foo, *args, bar='some default'):
    # do something

那么调用就不会等价了。 Mock 不知道实际函数的签名,因此它不能依赖我们在第一种情况下的等价性。

有没有一种方法可以通过让 Mock(或断言辅助函数或similar) 知道被 mock 替换的实际功能吗?

Mock 对象可以通过可选的 spec argument or with autospeccing 知道它所替换的对象(可以是函数或方法),但那些服务于不同的目的(限制对允许模拟)并且不影响事后检查。

The Mock object can be made aware of the object it replaces (which can be a function or method) with the optional spec argument or with autospeccing, but those serve a different purpose..

这正是 Issue 17015: mock could be smarter and inspect the spec's signature 改进问题的内容。 spec 实际上非常相关,现在使模拟函数签名感知。

看看当我们断言 mock 是使用关键字参数调用时 mock 是如何失败的——而不让它知道实际的函数签名:

>>> from unittest.mock import Mock
>>>
>>> def actual_fu(foo, bar):
...     pass
>>>
>>> m = Mock()
>>> m('foo', bar='baz')
<Mock name='mock()' id='4356741496'>
>>>
>>> m.assert_called_once_with(foo='foo', bar='baz')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/unittest/mock.py", line 803, in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/unittest/mock.py", line 792, in assert_called_with
    raise AssertionError(_error_message()) from cause
AssertionError: Expected call: mock(bar='baz', foo='foo')
Actual call: mock('foo', bar='baz')

现在,如果我们提供 spec:

,看看这一切如何通过
>>> m = Mock(spec=actual_fu)
>>> m('foo', bar='baz')
<Mock name='mock()' id='4356249528'>
>>> 
>>> m.assert_called_once_with(foo='foo', bar='baz')
>>> m.assert_called_once_with('foo', bar='baz')
>>> m.assert_called_once_with(bar='baz', foo='foo')
>>> m.assert_called_once_with('foo', 'baz')
>>>

(使用 Python 3.5.1)