模拟一个类:使用Mock()还是patch()?
我在用mock这个库来做Python编程,想知道这两种方法哪种更好(也就是更符合Python的风格)。
方法一:直接创建一个模拟对象,然后使用它。代码看起来是这样的:
def test_one (self):
mock = Mock()
mock.method.return_value = True
# This should call mock.method and check the result.
self.sut.something(mock)
self.assertTrue(mock.method.called)
方法二:使用patch来创建一个模拟。代码看起来是这样的:
@patch("MyClass")
def test_two (self, mock):
instance = mock.return_value
instance.method.return_value = True
# This should call mock.method and check the result.
self.sut.something(instance)
self.assertTrue(instance.method.called)
这两种方法的效果是一样的。我不太清楚它们之间有什么区别。
有没有人能给我解释一下?
3 个回答
关于使用 unittest.mock 的关键点
- 如果你想替换被测试对象的一些接口元素(比如传递的参数),就用 Mock。
- 如果你想替换被测试对象内部对某些对象和导入模块的调用,就用 patch。
- 一定要提供你正在模拟的对象的规格说明(spec):
- 使用 patch 时,你可以提供 autospec。
- 使用 Mock 时,你可以提供 spec。
- 除了 Mock,你还可以使用 create_autospec,这个是用来创建带有规格说明的 Mock 对象。
在上面的问题中,正确的做法是使用 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)
使用了定义规格的测试用例会失败,因为从 something
和 second
函数调用的方法不符合 MyClass,这意味着它们能捕捉到错误,而默认的 Mock
则不会。
另外,还有一个选项:使用 patch.object 来模拟仅被调用的类方法。
使用 patch 的好情况是当类作为函数内部的一部分时:
def something():
arg = 1
return MyClass.method(arg)
这时你会想用 patch 作为装饰器来模拟 MyClass。
我有一个关于这个的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)
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
就很合适了。