
当函数在类属性中被直接调用(而非实例化时),其执行发生在模块导入阶段,早于 `@patch` 的生效时机;因此需结合 `patch.object` 与 `importlib.reload` 重新加载模块,才能使类属性获取到模拟后的返回值。
在 Python 单元测试中,模拟(mock)类属性中预计算的函数调用是一个常见但易出错的场景。根本原因在于:类属性的赋值语句(如 class_attribute = bar())在类定义时即被执行,而此时模块尚未被 patch —— 它发生在模块首次被 import 的过程中,远早于测试方法中的 @patch 装饰器生效时间。
这意味着,即使你用 @patch("foo.bar") 修饰测试方法,Foo.class_attribute 早已在 import foo 时被赋值为 "bar" 的原始返回值;后续 patch 只会影响之后对 bar() 的调用(例如在 __init__ 中),而无法回溯修改已存在的类属性。
✅ 正确解法是:在 patch 目标函数的同时,强制重载(reload)依赖该函数的模块,使其类定义被重新执行,从而触发 patched 版本的 bar() 调用。
以下是可直接运行的修复示例:
立即学习“Python免费学习笔记(深入)”;
# test_foo.py
import importlib
import unittest
from unittest import mock
import foo
import methods # 注意:必须显式导入被 patch 的源模块
class FooTestCase(unittest.TestCase):
def test_mock_class_attribute_at_definition_time(self):
expected = "patched foo"
# 使用 patch.object 精准定位并替换 methods.bar
with mock.patch.object(methods, "bar", return_value=expected):
# 关键步骤:重载 foo 模块,触发 Foo 类重新定义
importlib.reload(foo)
# 此时 Foo.class_attribute 已由 patched bar() 计算得出
self.assertEqual(foo.Foo.class_attribute, expected)
self.assertEqual(foo.Foo().class_attribute, expected) # 实例属性也一致⚠️ 注意事项:
- 必须提前 import methods:patch.object 需要真实模块对象作为 target,不能传字符串;
- importlib.reload() 作用于已被导入的模块对象(如 foo),而非模块名字符串;
- 避免在测试类顶层 reload:应放在 with 块内,确保每次测试隔离性;
- 不推荐全局 reload 或修改 sys.modules:易引发副作用,破坏测试纯净性;
- 若项目使用 pytest,可借助 pytest-mock 或 monkeypatch,但底层逻辑相同:先 patch 函数,再 reload 模块。
总结:对“定义期求值”的类属性进行 mock,本质是控制模块加载时机。patch.object + importlib.reload 是标准、可靠且符合 Python 导入机制的解决方案,适用于所有类似场景(如配置类、常量生成器、预编译正则等)。










