模拟一个类:Mock()或patch()?

Sar*_*ica 105 python unit-testing mocking

我正在使用Python的模拟,并想知道这两种方法中的哪一种更好(阅读:更多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)
Run Code Online (Sandbox Code Playgroud)

方法二:使用补丁创建模拟.代码如下:

@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)
Run Code Online (Sandbox Code Playgroud)

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

谁能开导我?

D.S*_*ley 143

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>
Run Code Online (Sandbox Code Playgroud)

patch替换MyClass方式允许您控制您调用的函数中的类的用法.修补类后,对类的引用将完全被mock实例替换.

mock.patch当你测试在测试中创建类的新实例的东西时,通常会使用它. mock.Mock实例更清晰,更受欢迎.如果您的self.sut.something方法创建了一个实例,MyClass而不是接收一个实例作为参数,那么mock.patch这里是合适的.

  • @ D.Shawley链接坏了,现在可以在这里找到:["在哪里修补"](https://docs.python.org/3/library/unittest.mock.html#where-to-patch) (5认同)
  • @ravz - 给[[Where to to Patch"](https://mock.readthedocs.org/en/latest/patch.html#where-to-patch)一个阅读.这是正常工作的难点之一. (4认同)
  • @ D.Shawley我们如何修补在另一个需要进行测试的类中实例化的类. (2认同)
  • 要修补 **class 对象**,请参阅 http://stackoverflow.com/questions/8469680/using-mock-patch-to-mock-a-class-method/34534635#34534635 (2认同)

Mik*_*Two 19

我有关于此的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)
Run Code Online (Sandbox Code Playgroud)


And*_*yko 10

解释差异并为使用unittest.mock提供指导的要点

  1. 如果要替换被测对象的某些界面元素(传递参数),请使用 Mock
  2. 如果要替换对某些对象的内部调用和被测对象的导入模块,请使用补丁
  3. 始终提供来自您正在模拟的对象的规范
    • 使用补丁,您始终可以提供autospec
    • 使用 Mock,您可以提供规格
    • 您可以使用create_autospec代替 Mock,它旨在创建具有规范的 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)
Run Code Online (Sandbox Code Playgroud)

使用已定义规范的测试用例失败,因为调用的方法somethingsecond函数与 MyClass兼容,这意味着 - 它们捕获错误,而默认值Mock将显示。

作为旁注,还有一个选项:使用patch.object来模拟调用的类方法。

当类用作函数的内部部分时,补丁的良好用例将是这种情况:

def something():
    arg = 1
    return MyClass.method(arg)
Run Code Online (Sandbox Code Playgroud)

然后你会想要使用补丁作为装饰器来模拟 MyClass。