模拟一个类:使用Mock()还是patch()?

200 投票
3 回答
227044 浏览
提问于 2025-04-17 06:34

我在用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 个回答

26

关于使用 unittest.mock 的关键点

  1. 如果你想替换被测试对象的一些接口元素(比如传递的参数),就用 Mock。
  2. 如果你想替换被测试对象内部对某些对象和导入模块的调用,就用 patch。
  3. 一定要提供你正在模拟的对象的规格说明(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)

使用了定义规格的测试用例会失败,因为从 somethingsecond 函数调用的方法不符合 MyClass,这意味着它们能捕捉到错误,而默认的 Mock 则不会。

另外,还有一个选项:使用 patch.object 来模拟仅被调用的类方法。

使用 patch 的好情况是当类作为函数内部的一部分时:

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

这时你会想用 patch 作为装饰器来模拟 MyClass。

60

我有一个关于这个的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)
211

mock.patchmock.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 就很合适了。

撰写回答