模拟与魔术模拟


138

我的理解是MagicMockMock的超集,它自动执行“魔术方法”,从而无缝地提供对列表,迭代等的支持。那么,为什么存在普通Mock的原因是什么?难道这不是MagicMock的简化版本,实际上可以忽略吗?Mock类是否知道MagicMock中没有的任何技巧?

Answers:


99

模拟存在的原因是什么?

Mock的作者Michael Foord 在Pycon 2011(31:00)上回答了一个非常类似的问题

问:为什么MagicMock做了一件单独的事情,而不仅仅是将功能折叠到默认的模拟对象中?

答:一个合理的答案是MagicMock的工作方式是通过创建新的Mocks并对其进行设置来预先配置所有这些协议方法,因此,如果每个新的模拟都创建了一堆新的模拟并将它们设置为协议方法,则所有这些协议方法创建了一堆更多的模拟并将它们设置在其协议方法上,您可以无限递归...

如果您想将模拟作为容器对象访问是错误的,那又不想怎么办?如果每个模拟都自动获得了每种协议方法,那么这样做就变得更加困难。而且,MagicMock为您执行了一些预配置,设置了可能不合适的返回值,所以我认为最好有这种便利,它具有所有预配置的功能并可供您使用,但是您也可以进行普通的模拟对象,然后配置您想要存在的魔术方法...

简单的答案是:如果您要这样做,那就在任何地方使用MagicMock。


12
我认为一个更好的答案是:如果知道自己在做什么,请使用MagicMock,否则请使用Mock。
laike9m

56

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

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


果然我已经阅读了文档。那没有回答我的问题-如果MagicMock完全一样而且还要更多,为什么还要打扰普通的Mock?我在测试中没有看到任何无关紧要的东西-只需使用其他名称即可。那么渔获量在哪里?
弗拉基米尔·伊格纳托夫2013年

39
测试应该是最少的,模拟对象的功能应该是最小的,这样您就可以确定要测试的对象是什么。如果仅因为is使用MagicMock会做更多,但没有明确测试所有“更多”,则可能会由于默认MagicMock行为而导致测试失败的风险。此失败可能反映出与MagicMock的默认设置有关的更多信息,而不是其应模拟的东西。更糟糕的是,如果测试应该失败,则冒着测试成功的风险。风险很小,但是如果发生这种情况,将会浪费大量时间。
肖恩·雷德蒙德2013年

1
我认为这就像使用普通的JS与Jquery。当然,您可以使用Jquery来完成所有JS,但是在某些情况下,您只想使用完成工作所需的最少工具即可。我发现这些情况通常要么极其简单,要么极其复杂。

49

首先MagicMock是的子类Mock

class MagicMock(MagicMixin, Mock)

结果,MagicMock提供了Mock提供的一切以及更多功能。与其认为Mock是MagicMock的精简版,不如认为MagicMock是Mock的扩展版。这应该解决您有关Mock为什么存在以及Mock在MagicMock之上提供了什么的问题。

其次,MagicMock提供了许多魔术方法的默认实现,而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呢?

回到您的问题是:您可以使用默认的魔术方法实现吗?例如,mocked_object[1]可以不出错吗?您是否可以接受由于魔术方法实现而导致的任何意外后果?

如果对这些问题的回答为“是”,请继续使用MagicMock。否则,坚持模拟。


12

这就是python的官方文档 所说的:

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


3

我发现了另一种特殊情况,其中简单 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


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

AssertionError如果您使用过,它将引发一个MagicMock

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.