修改一个项目的 Unit Test 代码时,遇到一个关于 mock 的问题点,花了我很久时间才,找到解决方案。特此记录一下,提醒自己,提示他人。Binder

我给一个开源软件贡献 PR 时,遇到一个大的问题是我总是要给我的代码写单元测试,会出现一些奇怪的问题(我以为的,实际最后往往证明是我蠢 :( ,本例就是)

经过简化,最小可复现问题代码集如下:

文件 dependency.py

def some_funny_func():
    return 'funny'

文件 module.py

from dependency import some_funny_func

def call_func():
    return some_funny_func()

文件 tester.py

from unittest.mock import patch

from module import call_func

def test_call_func():
    def mocked_funny_func():
        return "not funny at all"

    with patch("dependency.some_funny_func", mocked_funny_func):
        return_value = call_func()

    assert return_value == "not funny at all"

一切看似合情合理(有些高手,可能已经发现问题了,但我当时没有看出来问题),但是就是通过不了测试。

这里的错误是,没有深入理解 patch 的工作原理,patch 通过修改 module 属性的方式工作。这里 from module import call_func 执行的时候已经经 dependency.some_funny_func 导入了 module, 换言之:module.some_funny_func 已经指向了 dependency.some_funny_func, 此时通过 patch("dependency.some_funny_func", mocked_funny_func) 只是修改了 dependency.some_funny_func 至新的 mocked_funny_func. 但不能修改 module.some_funny_func, 因为这个是修改前赋值的,它现在依旧指向原来的函数。

将上述改写成 python 代码,原理大概如下:

# 模拟 dependency.some_funny_func
dependency = {}
dependency["some_funny_func"] = "some_value"

# 模拟 module.some_funny_func
module = {}
module["some_funny_func"] = dependency["some_funny_func"]

# 模拟 mock
dependency["some_funny_func"] = "some_other_value"

# 查看结果
print(dependency["some_funny_func"])
print(module["some_funny_func"])

在线演示

在线演示地址