如何在 Python 中正确模拟类定义时执行的函数调用

11次阅读

如何在 Python 中正确模拟类定义时执行的函数调用

当函数在类属性中被直接调用(而非实例化时),其执行发生在模块导入阶段,早于 `@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 导入机制的解决方案,适用于所有类似场景(如配置类、常量生成器、预编译正则等)。

text=ZqhQzanResources