使用 patch to 模拟一个函数(而不是一个方法)

Using patch to to mock a function (as opposed to a method)

我想做类似下面的例子(找到here

>>> with patch.object(ProductionClass, 'method', return_value=None) as mock_method:
...     thing = ProductionClass()
...     thing.method(1, 2, 3)

然而,这是在 ProductionClass 上修补名为 method 的方法。我想在上下文中修补通用函数。理想情况下看起来像...

with path.something(my_fn, return_value=my_return) as mock_function:
    do_some_other_fn()

my_fndo_some_other_fn 中被调用得很深,因此很难直接模拟出来。这似乎应该是直截了当的,但我找不到正确的语法

EDITdo_some_other_fn 所在的模块中,我导入 my_fn 如下

from my_module import my_fn

所以我需要一种方法来告诉 mock 从模块外部对其进行修补。这可能吗?

编辑 2 我认为这让我更清楚我在寻找什么

这可行但不理想:

import my_module
with patch('my_module.fn', return_value='hello') as patch_context:
    x = my_module.fn()
    # x now contains 'hello'

但是我更希望它像这样(或类似的东西)工作

from my_module import fn
with patch('my_module.fn', return_value='hello') as patch_context:
    x = fn()
    # x contains real result from real call to fn()

你可以看到类似模块对象静态方法的函数。要修补模块 mymodule 中的函数 func,您可以使用

patch("mymodule.func", return_value=my_return)

您应该注意 Where to patch,如果该函数在您进行测试的同一模块中,则应使用 "__main__.func" 作为补丁参数。

patchpatch.object 可以用作装饰器、上下文或通过 start()stop() 方法。

现在,当您在一个模块中从另一个模块导入函数时,例如:

from mymodule import func as foo

您在名为 foo 的新模块中创建了对 func 的新引用。 每个 在此模块中调用 foo 的时间,您将使用导入时加载的对 mymodule.func 的引用:如果您想更改此行为,您应该在新模块中修补 foo

为了更清楚地说明,我构建了一个示例,其中 mymodule 包含 funcmodule_a 包含 mymodule 并使用 mymodule.funcmodule_b 使用 from mymodule import func as foo 并使用机器人 foomymodule.func

mymodule.py

def func():
    return "orig"

module_a.py

import mymodule

def a():
    return mymodule.func()

module_b.py

from mymodule import func as foo
import mymodule

def b_foo():
    return foo()

def b():
    return mymodule.func()

test.py

import unittest
from unittest.mock import *
import mymodule
import module_a
import module_b

class Test(unittest.TestCase):
    def test_direct(self):
        self.assertEqual(mymodule.func(), "orig")
        with patch("mymodule.func", return_value="patched"):
            self.assertEqual(mymodule.func(), "patched")

    def test_module_a(self):
        self.assertEqual(module_a.a(), "orig")
        with patch("mymodule.func", return_value="patched"):
            self.assertEqual(module_a.a(), "patched")

    def test_module_b(self):
        self.assertEqual(module_b.b(), "orig")
        with patch("mymodule.func", return_value="patched"):
            self.assertEqual(module_b.b(), "patched")
            self.assertEqual(module_b.b_foo(), "orig")
        with patch("module_b.foo", return_value="patched"):
            self.assertEqual(module_b.b(), "orig")
            self.assertEqual(module_b.b_foo(), "patched")    


if __name__ == '__main__':
    unittest.main()

换句话说,选择修补位置的真正规则是如何在 您想使用修补版本 的地方引用函数。

您尝试使用 from my_module import fn 进行修补无效,因为 import 语句创建了一个本地符号 fn,它指向 fnmy_module 中的任何值 导入时。您稍后修补 my_module.fn 但这并不重要,因为您已经拥有 fn.

的本地副本

如果包含 patch 调用的文件是主模块(python 最初加载的文件),您应该可以通过修补 __main__.fn:

from my_module import fn
with patch('__main__.fn', return_value='hello') as patch_context:
    x = fn()

如果包含 patch 调用的文件作为模块从主模块加载,则 __main__ 将不起作用,您需要传递包含的模块的绝对模块名称你 patch 呼叫 patch 而不是 __main__.