@mock.patch 如何知道每个模拟对象使用哪个参数?

问题描述 投票:0回答:3

看这个网页:http://www.toptal.com/python/an-introduction-to-mocking-in-python——作者谈论了Python中的Mocking和Patching,并给出了相当扎实的“真实-世界”的例子。让我困惑的部分是理解单元测试框架如何知道哪个模拟对象传递到哪个补丁。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import os.path

def rm(filename):
    if os.path.isfile(filename):
        os.remove(filename)

代码示例非常容易理解。对操作系统库/模块的硬编码依赖。首先使用

os.path.isfile()
方法检查文件是否存在,如果存在,则使用
os.remove()

将其删除

测试/模拟代码如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from mymodule import rm

import mock
import unittest

class RmTestCase(unittest.TestCase):

    @mock.patch('mymodule.os.path')
    @mock.patch('mymodule.os')
    def test_rm(self, mock_os, mock_path):
        # set up the mock
        mock_path.isfile.return_value = False

        rm("any path")

        # test that the remove call was NOT called.
        self.assertFalse(mock_os.remove.called, "Failed to not remove the file if not present.")

        # make the file 'exist'
        mock_path.isfile.return_value = True

        rm("any path")

        mock_os.remove.assert_called_with("any path")

我想让我困惑的是测试中有 2 个 @Patch 调用和 2 个参数传递。单元测试框架如何知道

mymodule.os.path
正在修补
os.path
并且它已映射到
mock_path
mymodule.os.path
是在哪里定义的?

(似乎有很多“魔法”正在发生,但我没有遵循它。)

python unit-testing mocking
3个回答
5
投票

它按照装饰器的执行顺序进行,这也是传递给测试方法的参数的顺序...

装饰器的执行顺序如下所示: https://thadeusb.com/weblog/2010/08/23/python_multiple_decorators/

当您按照编写的方式使用 patch 时,它会自动为您创建一个 Mock 实例,并将其作为参数传递给您的测试方法。还有另一个版本:

@mock.patch("subprocess.check_output", mock.MagicMock(return_value='True'))
def test_mockCheckOutput(self):
    self.assertTrue(subprocess.check_output(args=[])=='True')

在本例中,您传递自己的 Mock 对象,在本示例中,当您调用 subprocess.check_output() 时,它将返回“True”

但是你可以这样做:

def test_mockCheckOutput(self):
    m = mock.MagicMock(return_value='True')
    with mock.patch("subprocess.check_output", m):
        self.assertTrue(subprocess.check_output(args=[])=='True')

在这种情况下,您可以传递任何您想要的模拟项目,因为它将在运行时评估...:)


3
投票

应用装饰器的时候,这样看就好了

<wrapper1>
    <wrapper2>
        <wrapper3>
           **Your Function**
        </wrapper3>
    </wrapper2>
</wrapper1>

基本上,您的函数需要按以下顺序与包装器交互:

wrapper3
->
wrapper2
->
wrapper1

@wrapper1
@wrapper2
@wrapper3
def your_func(wrapper1.input, wrapper2.input, wrapper3.input):

注意wrapper1.input并不是您实际引用其输入的方式

回答你问题的第二部分,

mymodule.os
如何知道引用 os.修补时,您可以有效拦截对该特定名称的调用。当您在
os
中调用
mymodule
时,您实际上是在调用
mymodule.os
。修补时,您必须通过实际代码中调用的方式来引用正在模拟的类,而不是从测试模块的角度来看


0
投票

补丁的顺序应该颠倒,因为它们是自下而上应用的。请参阅 python 文档 中有关嵌套模拟参数的评论:

注意当您嵌套补丁装饰器时,模拟将被传递到 装饰函数的顺序与它们应用的顺序相同(普通的 python 应用装饰器的顺序)。这意味着从下到上,所以 在上面的示例中,传入了

module.ClassName1
的模拟 首先。

© www.soinside.com 2019 - 2024. All rights reserved.