嘲笑类: Mock ()还是 patch () ?

我在 Python 中使用 嘲笑,想知道这两种方法中哪一种更好(请阅读: more pythonic)。

方法一 : 创建一个模拟对象并使用它:

def test_one (self):
mock = Mock()
mock.method.return_value = True
self.sut.something(mock) # This should called mock.method and checks the result.
self.assertTrue(mock.method.called)

方法2 : 使用补丁创建一个 mock:

@patch("MyClass")
def test_two (self, mock):
instance = mock.return_value
instance.method.return_value = True
self.sut.something(instance) # This should called mock.method and checks the result.
self.assertTrue(instance.method.called)

这两种方法做同样的事情。我不确定的差异。

有人能告诉我吗?

177503 次浏览

mock.patch 是一种与 mock.Mock非常不同的生物。使用模拟对象的类 patch取代,并允许您使用模拟实例。看看这个片段:

>>> class MyClass(object):
...   def __init__(self):
...     print 'Created MyClass@{0}'.format(id(self))
...
>>> def create_instance():
...   return MyClass()
...
>>> x = create_instance()
Created MyClass@4299548304
>>>
>>> @mock.patch('__main__.MyClass')
... def create_instance2(MyClass):
...   MyClass.return_value = 'foo'
...   return create_instance()
...
>>> i = create_instance2()
>>> i
'foo'
>>> def create_instance():
...   print MyClass
...   return MyClass()
...
>>> create_instance2()
<mock.Mock object at 0x100505d90>
'foo'
>>> create_instance()
<class '__main__.MyClass'>
Created MyClass@4300234128
<__main__.MyClass object at 0x100505d90>

patch在某种程度上取代了 MyClass,允许您控制类在调用的函数中的使用。修补类之后,对该类的引用将被模拟实例完全替换。

mock.patch通常用于测试在测试内部创建类的新实例的内容。mock.Mock实例更加清晰,是首选。如果您的 self.sut.something方法创建了 MyClass的一个实例,而不是接收一个实例作为参数,那么 mock.patch在这里是合适的。

我对这个进行了 YouTube 视频测试。

简短的回答: 在传递想要被嘲笑的内容时使用 mock,如果没有,则使用 patch。在这两者中,mock 是最受欢迎的,因为它意味着您正在使用适当的依赖注入编写代码。

愚蠢的例子:

# Use a mock to test this.
my_custom_tweeter(twitter_api, sentence):
sentence.replace('cks','x')   # We're cool and hip.
twitter_api.send(sentence)


# Use a patch to mock out twitter_api. You have to patch the Twitter() module/class
# and have it return a mock. Much uglier, but sometimes necessary.
my_badly_written_tweeter(sentence):
twitter_api = Twitter(user="XXX", password="YYY")
sentence.replace('cks','x')
twitter_api.send(sentence)

解释差异并为使用 Unittest Mock提供指导的关键点

  1. 如果要替换被测对象的某些接口元素(传递参数) ,请使用 Mock
  2. 如果要替换对某些对象的内部调用和被测对象的导入模块,请使用补丁
  3. 始终 提供您正在模仿的对象的规格说明

在上面的问题中,正确的答案是使用 Mock,或者更精确地说是 create_autospec(因为它会给你正在模仿的类的模拟方法添加规范) ,在模拟方法上定义的 spec对于尝试调用不存在的类的方法(无论签名)是有帮助的,请参阅一些

from unittest import TestCase
from unittest.mock import Mock, create_autospec, patch




class MyClass:
    

@staticmethod
def method(foo, bar):
print(foo)




def something(some_class: MyClass):
arg = 1
# Would fail becuase of wrong parameters passed to methd.
return some_class.method(arg)




def second(some_class: MyClass):
arg = 1
return some_class.unexisted_method(arg)




class TestSomethingTestCase(TestCase):
def test_something_with_autospec(self):
mock = create_autospec(MyClass)
mock.method.return_value = True
# Fails because of signature misuse.
result = something(mock)
self.assertTrue(result)
self.assertTrue(mock.method.called)
    

def test_something(self):
mock = Mock()  # Note that Mock(spec=MyClass) will also pass, because signatures of mock don't have spec.
mock.method.return_value = True
        

result = something(mock)
        

self.assertTrue(result)
self.assertTrue(mock.method.called)
        

def test_second_with_patch_autospec(self):
with patch(f'{__name__}.MyClass', autospec=True) as mock:
# Fails because of signature misuse.
result = second(mock)
self.assertTrue(result)
self.assertTrue(mock.unexisted_method.called)




class TestSecondTestCase(TestCase):
def test_second_with_autospec(self):
mock = Mock(spec=MyClass)
# Fails because of signature misuse.
result = second(mock)
self.assertTrue(result)
self.assertTrue(mock.unexisted_method.called)
    

def test_second_with_patch_autospec(self):
with patch(f'{__name__}.MyClass', autospec=True) as mock:
# Fails because of signature misuse.
result = second(mock)
self.assertTrue(result)
self.assertTrue(mock.unexisted_method.called)
    

def test_second(self):
mock = Mock()
mock.unexisted_method.return_value = True
        

result = second(mock)
        

self.assertTrue(result)
self.assertTrue(mock.unexisted_method.called)

定义了规范的测试用例使用 失败,因为从 somethingsecond调用的方法使用 MyClass 函数 不是抱怨,这意味着-它们捕获 bug,而默认的 Mock将显示。

另外还有一个选项: 使用 Patch.object模拟调用的类方法。

当类作为函数的内部部分使用时,补丁的好用例是:

def something():
arg = 1
return MyClass.method(arg)

然后,您将希望使用补丁作为一个装饰器来模仿 MyClass。