模拟与魔术模拟

问题:模拟与魔术模拟

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

My understanding is that MagicMock is a superset of Mock that automatically does “magic methods” thus seamlessly providing support for lists, iterations and so on… Then what is the reason for plain Mock existing? Isn’t that just a stripped down version of MagicMock that can be practically ignored? Does Mock class know any tricks that are not available in MagicMock?


回答 0

模拟存在的原因是什么?

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

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

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

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

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

What is the reason for plain Mock existing?

Mock’s author, Michael Foord, addressed a very similar question at 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.


回答 1

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

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

With Mock you can mock magic methods but you have to define them. MagicMock has “default implementations of most of the magic methods.”.

If you don’t need to test any magic methods, Mock is adequate and doesn’t bring a lot of extraneous things into your tests. If you need to test a lot of magic methods MagicMock will save you some time.


回答 2

首先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。否则,坚持模拟。

To begin with, MagicMock is a subclass of Mock.

class MagicMock(MagicMixin, Mock)

As a result, MagicMock provides everything that Mock provides and more. Rather than thinking of Mock as being a stripped down version of MagicMock, think of MagicMock as an extended version of Mock. This should address your questions about why Mock exists and what does Mock provide on top of MagicMock.

Secondly, MagicMock provides default implementations of many/most magic methods, whereas Mock doesn’t. See here for more information on the magic methods provided.

Some examples of provided magic methods:

>>> 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

And these which may not be as intuitive (at least not intuitive to me):

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

You can “see” the methods added to MagicMock as those methods are invoked for the first time:

>>> 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', ...]

So, why not use MagicMock all the time?

The question back to you is: Are you okay with the default magic method implementations? For example, is it okay for mocked_object[1] to not error? Are you okay with any unintended consequences due to the magic method implementations being already there?

If the answer to these questions is a yes, then go ahead and use MagicMock. Otherwise, stick to Mock.


回答 3

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

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

This is what python’s official documentation says:

In most of these examples the Mock and MagicMock classes are interchangeable. As the MagicMock is the more capable class it makes a sensible one to use by default.


回答 4

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

I’ve found another particular case where simple Mock may turn more useful than 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

Comparing against ANY can be useful, for example, comparing almost every key between two dictionaries where some value is calculated using a mock.

This will be valid if you’re using Mock:


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

while it will raise an AssertionError if you’ve used MagicMock