Django unittest中mock.patch点分路径随运行方式变化如何解决?
Python单元测试mock路径飘移问题
问题描述
花费数小时梳理目标代码的patch方案后,发现补丁对应的点分路径完全超出预期:根据测试运行方式、执行命令时的工作目录不同,待patch模块的点分路径会动态变化,对单元测试非常不友好。
当前使用__package__.replace('.tests', '.typers.load_palette')的方式动态拼接路径——当前包路径可能为bar.tests、foo.bar.tests或其他值,替换后拼接得到目标路径,这种写法属于极不规范的hack。原本期望mock.patch支持..typers.load_palette这类相对点分路径,但实际该写法不被支持,需要找到正规的规避方案。
项目文件结构
loaders.py # 包含需要被patch的load_palette()函数 typers.py # 存在`from .loaders import load_palette`导入语句,内部会调用load_palette() render.py # 包含的业务函数会调用typers模块中的对应函数 tests/test_render.py # render模块测试用例,调用链为:测试用例 -> render函数 -> typers函数 -> load_palette()
现有临时实现
# file: test_render.py # 路径会随测试执行位置变化,因此动态生成 @mock.patch(__package__.replace('.tests', '.typers.load_palette'), return_value=mocks.palette) class render_rule_Tests(SimpleTestCase): def test_render_preset_rule(self, _): # _ 为mock后的load_palette对象 ...
正确解决方案
mock patch的核心原则:patch的目标永远是「被测代码实际引用该对象的命名空间位置」,而非对象原始定义的位置,按以下方式实现即可完全规避路径飘移问题:
- 放弃字符串形式的点分路径patch方式,改用
mock.patch.object直接修改已导入模块的属性。 - 在测试文件顶部通过绝对导入拿到持有目标引用的模块对象:本场景中
load_palette被typers模块导入后存在于typers的命名空间,调用时也是从typers自身命名空间查找,因此直接导入typers模块即可。 - 直接patch模块对象上的目标属性,全程不需要拼接字符串路径,完全不受测试启动时的工作目录影响。
修正后的测试代码示例:
# file: test_render.py # 顶部通过绝对导入拿到typers模块对象,替换成你实际的项目顶层包名即可 from your_project import typers # 直接patch模块对象上的load_palette属性,不需要解析字符串路径 @mock.patch.object(typers, 'load_palette', return_value=mocks.palette) class render_rule_Tests(SimpleTestCase): def test_render_preset_rule(self, mocked_load_palette): # 正常编写测试逻辑即可 ...
注意事项
- 不要尝试patch
loaders.load_palette原始定义位置。因为typers使用from .loaders import load_palette语法时,已经把函数引用直接复制到了自身命名空间,patch原始位置不会替换typers中已经存在的引用,会导致mock不生效。 - 导入模块时必须使用项目的标准绝对导入,不要在测试文件中使用相对导入拿到目标模块,避免导入路径不一致的问题。
内容的提问来源于stack exchange,提问作者run_the_race
相关产品推荐
相关产品推荐

