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

How does @mock.patch know which parameter to use for each mock object?

查看此网页:http://www.toptal.com/python/an-introduction-to-mocking-in-python -- 作者在 Python 中讨论了 Mocking 和 Patching,并给出了一个非常可靠的示例 "real-world"。让我感到困惑的部分是理解单元测试框架如何知道将哪个模拟对象传递给哪个补丁。

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

import os
import os.path

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

代码示例非常容易理解。对 OS library/module 的硬编码依赖。首先使用 os.path.isfile() 方法检查文件是否存在,如果存在,则使用 os.remove()

将其删除

Test/Mock代码如下:

#!/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_pathmymodule.os.path 在哪里定义?

(似乎有很多 "magic" 正在进行,我没有关注它。)

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

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

当你按照你编写的方式使用补丁时,它会自动为你创建一个 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() 时,它将 return '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')

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

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

<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。修补时,您实际上是在拦截对该特定名称的调用。当您在 mymodule 中调用 os 时,您实际上是在调用 mymodule.os。修补时,您必须参考在实际代码中被调用的方式所模拟的 class,而不是从测试模块的角度来看