我的理解是MagicMock是一个Mock的超集,它自动执行“魔法方法”,从而无缝地提供对列表、迭代等的支持……那么plain Mock存在的原因是什么?这难道不是一个可以被忽略的MagicMock的精简版吗?Mock类知道在MagicMock中不可用的任何技巧吗?


使用Mock,你可以模拟魔术方法,但你必须定义它们。MagicMock拥有“大多数magic方法的默认实现”。

如果您不需要测试任何神奇的方法,Mock就足够了,并且不会给您的测试带来很多无关的东西。如果您需要测试很多魔术方法,MagicMock将为您节省一些时间。


这是python的官方文档 说:

在大多数示例中,Mock和MagicMock类是可互换的。由于MagicMock是功能更强的类,因此在默认情况下使用它是明智的。


plain Mock存在的原因是什么?

Mock的作者Michael ford在Pycon 2011(31:00)上提出了一个非常相似的问题:

Q: Why was MagicMock made a separate thing rather than just folding the ability into the default mock object? A: One reasonable answer is that the way MagicMock works is that it preconfigures all these protocol methods by creating new Mocks and setting them, so if every new mock created a bunch of new mocks and set those as protocol methods and then all of those protocol methods created a bunch more mocks and set them on their protocol methods, you've got infinite recursion... What if you want accessing your mock as a container object to be an error -- you don't want that to work? If every mock has automatically got every protocol method, then it becomes much more difficult to do that. And also, MagicMock does some of this preconfiguring for you, setting return values that might not be appropriate, so I thought it would be better to have this convenience one that has everything preconfigured and available for you, but you can also take a ordinary mock object and just configure the magic methods you want to exist... The simple answer is: just use MagicMock everywhere if that's the behavior you want.


首先,MagicMock是Mock的一个子类。

class MagicMock(MagicMixin, Mock)

因此,MagicMock提供了Mock所提供的一切,甚至更多。不要把Mock看作是MagicMock的简化版本,而应该把MagicMock看作是Mock的扩展版本。这将解决您的问题,即为什么存在Mock以及Mock在MagicMock之上提供了什么。

其次,MagicMock提供了许多/大多数magic方法的默认实现,而Mock没有。有关魔术方法的更多信息,请参见这里。

一些魔术方法的例子:

>>> int(Mock())
TypeError: int() argument must be a string or a number, not 'Mock'
>>> int(MagicMock())
1
>>> len(Mock())
TypeError: object of type 'Mock' has no len()
>>> len(MagicMock())
0

这些可能不是很直观(至少对我来说不是很直观):

>>> with MagicMock():
...     print 'hello world'
...
hello world
>>> MagicMock()[1]
<MagicMock name='mock.__getitem__()' id='4385349968'>

当这些方法第一次被调用时,你可以“看到”添加到MagicMock的方法:

>>> magic1 = MagicMock()
>>> dir(magic1)
['assert_any_call', 'assert_called_once_with', ...]
>>> int(magic1)
1
>>> dir(magic1)
['__int__', 'assert_any_call', 'assert_called_once_with', ...]
>>> len(magic1)
0
>>> dir(magic1)
['__int__', '__len__', 'assert_any_call', 'assert_called_once_with', ...]

那么,为什么不一直使用MagicMock呢?

回到您的问题是:您是否同意默认的magic方法实现?例如,mocked_object[1]是否可以不出错?您是否可以接受由于魔术方法实现已经存在而产生的任何意外后果?

如果这些问题的答案是肯定的,那么继续使用MagicMock。否则,请继续使用Mock。


我发现了另一个简单Mock可能比MagicMock更有用的特殊情况:

In [1]: from unittest.mock import Mock, MagicMock, ANY
In [2]: mock = Mock()
In [3]: magic = MagicMock()
In [4]: mock.foo == ANY
Out[4]: True
In [5]: magic.foo == ANY
Out[5]: False

比较ANY可能很有用,例如,比较两个字典之间的几乎每个键,其中一些值是使用mock计算的。

这将是有效的,如果你使用Mock:


self.assertDictEqual(my_dict, {
  'hello': 'world',
  'another': ANY
})

而如果你使用MagicMock,它将引发AssertionError