Mock与MagicMock的区别

230

我的理解是MagicMockMock的超集,自动执行“魔术方法”,从而无缝支持列表、迭代等操作...那么为什么还需要原始的Mock存在呢?它不就是一个可以被忽略的简化版本的MagicMock吗?Mock类是否知道任何在MagicMock中不可用的技巧呢?

5个回答

148

为什么需要使用纯粹的Mock?

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

Q:为什么要将MagicMock作为独立的事物,而不是将其能力合并到默认的mock对象中?

A:其中一个合理的答案是,MagicMock的工作方式是通过创建新的Mock并设置它们来预配置所有这些协议方法,因此如果每个新的mock创建了一堆新的mock并将这些mock设置为协议方法,然后所有这些协议方法都创建了一堆更多的mock并将它们设置在它们的协议方法中,那么就会产生无限递归……

如果你希望访问你的mock作为容器对象时会出现错误——你不希望它起作用怎么办?如果每个mock都自动获取了每个协议方法,那么就变得更加困难。另外,MagicMock还为你做了一些预配置,设置可能不合适的返回值,所以我认为最好有一个方便的方法,将所有的预配置和可用性都预先配置好,但你也可以使用普通的mock对象,只需配置你想要存在的魔术方法即可……

简单的答案是:如果这是你想要的行为,那就在任何地方都使用MagicMock。


38
我认为更好的回答是:如果你知道该怎么做,就使用 MagicMock,否则请使用 Mock。 - laike9m
11
我理解这段话的意思是:@laike9m认为应该优先使用MagicMock,除非你知道自己在做什么并且需要特定的行为,这种情况下可以使用Mock并进行定制。 - Robino
3
@Robino 我不这么认为。Sean Redmond 回答下面的评论已经很好地解释了。 - laike9m
3
官方文档建议使用MagicMock类作为默认选择,“由于MagicMock类更加强大,因此它是一个明智的默认选择。” https://docs.python.org/dev/library/unittest.mock-examples.html#mock-patching-methods - Robino
此外,unittest.mock.create_autospec() 会给你一个 MagicMock。你应该使用它。 - z33k
你可能想使用一个自定义的包装类来提高可读性: https://stackoverflow.com/questions/76564857/how-to-improve-readabilty-of-patch-and-magicmock-statements-and-avoid-string-id/ - Stefan

97

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

如果您不需要测试任何魔术方法,则Mock就足够了,并且不会在测试中带来许多无关的东西。如果您需要测试许多魔术方法,则使用MagicMock可以节省一些时间。


3
我确实已经阅读了文档。但这并没有回答我的问题——如果MagicMock可以做到完全一样的事情并且更多,那为什么还要费心去使用普通的Mock?我在我的测试中没有看到任何多余的东西——只需要使用不同的名称就可以了。所以问题出在哪里呢? - Vladimir Ignatov
65
测试应该尽可能地简单,并且模拟对象应该具有最小的功能,以便确切地知道你正在测试什么。如果你只是因为 MagicMock 做更多的事情而使用它,但你没有显式地测试所有那些“更多”的功能,那么你就会面临一个测试失败的风险,因为默认的 MagicMock 行为可能会反映出关于 MagicMock 默认值的问题,而不是被模拟的对象本身。更糟糕的是,你可能会在本应该失败的情况下得到了测试成功的结果。虽然这种风险很小,但如果发生了这种情况,就会浪费你很多时间。 - Sean Redmond
3
我认为它就像使用纯JS和Jquery一样。当然,你可以使用Jquery来完成所有JS工作,但在某些情况下,你只想使用最基本的工具来完成工作。我发现这种情况通常是非常简单或非常复杂的情况。 - snuggles

91

首先,MagicMockMock 的子类。

class MagicMock(MagicMixin, Mock)

作为结果,MagicMock 提供了 Mock 提供的一切,甚至更多。不要认为 Mock 是 MagicMock 的简化版本,而应该将 MagicMock 视为 Mock 的扩展版本。这应该可以解答您关于 Mock 为什么存在以及 Mock 在 MagicMock 基础上提供了什么的问题。
其次,MagicMock 提供了许多/大多数魔术方法的默认实现,而 Mock 没有。请参见here获取有关提供的魔术方法的更多信息。
以下是一些提供的魔术方法的示例:
>>> 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'>

你可以通过第一次调用方法来“看到”添加到魔术方法的方法:
>>> 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。否则,坚持使用Mock。


26
这是Python官方文档的说法:
在大多数示例中,Mock和MagicMock类是可以互换的。由于MagicMock是更强大的类,因此默认情况下使用它是明智的选择。

4
这对我来说听起来不太对。我喜欢使用最简单的工具来满足我的需求,而不是那些我不知道是否有用的功能最多的工具。 - cambunctious

9

我发现另一个情况,简单的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
})

如果你使用了MagicMock,它会引发一个AssertionError异常。


你能否解释一下为什么 MagicMock 对象会引发 AssertionError? - Singh
@Singh它没有满足断言,因此会引发AssertionError...你是什么意思? - Manu Artero

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接