【问题标题】:How does @mock.patch know which parameter to use for each mock object?@mock.patch 如何知道每个模拟对象使用哪个参数?
【发布时间】:2015-10-26 19:17:54
【问题描述】:

看看这个网页: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_pathmymodule.os.path 在哪里定义?

(似乎有很多“魔法”在发生,我没有关注它。)

【问题讨论】:

    标签: python unit-testing mocking


    【解决方案1】:

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

    装饰器的执行顺序如下所示: 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() 时,它将返回 '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')
    

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

    【讨论】:

    • 好的,这回答了我的一半问题。 (实际上,我会说它按倒序排列,但我明白您在链接到的页面中的意思。)下一步:Mock 对象是在哪里创建的?我看到我可以在测试中访问它(因为它是在我的装饰器中传递的。)但是如果我想在这个方法之外显式地创建它,比如在 Setup() 方法中呢?我该怎么做?
    • 当您按照自己编写的方式使用补丁时,它会自动为您创建。还有另一个版本:@mock.patch("subprocess.check_output", mock.MagicMock(return_value='True')) def test_mockCheckOutput(self): 在这种情况下,你传递你自己的 Mock 对象,在这个例子中,当你调用 subprocess.check_output(),它会返回 'True'
    • 随意编辑您的答案,以便您的代码以正确的格式显示(以帮助以后阅读此内容的其他人。):)
    • 啊,所以我可以创建一个m = mock.MagicMock(),然后将其修改为我心中的内容,并使用@mock.patch("mymodule.os.path", m) 行插入它。是吗?
    • 在分配给补丁之前,您必须在全局命名空间中将 m 修改为您心中的内容,并且一旦分配就无法修改它...
    【解决方案2】:

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

    <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。修补时,您必须参考在实际代码中调用它的方式来模拟的类,而不是从测试模块的角度来看

    【讨论】:

      猜你喜欢
      • 2020-07-11
      • 1970-01-01
      • 1970-01-01
      • 2018-06-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-10
      • 2022-01-01
      相关资源
      最近更新 更多