You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用@patch.object mock模块函数作用域外导入的Logger类方法?

如何Mock模块全局作用域中Logger类的__init__和write方法

我正在尝试用@patch.object来MockLogger类的__init__方法以及log.write()方法,但这个类是在模块全局作用域导入并使用的,不是在函数内部。之前看到的教程都是针对函数内使用的场景打补丁,现在需要同时Mockmain.py和routers.py里的Logger行为,该怎么做?

相关代码文件

src/apis/main.py

from utils.log import Logger
from routes import route

log = Logger(name="logger-1")
log.write("logger started")

def main():
    log = Logger(name="logger-1")
    log.write("inside main")
    route()

if __name__ == "__main__":
    import logging
    logging.basicConfig(level=logging.INFO)  # for demo
    main()

src/apis/routers/routes.py

from utils.log import Logger
log = Logger(name="logger-1")
log.write(message=f"Inside route")
def route():
    log.write(message=f"Logging done.")

utils/log/logging.py

import logging
class Logger:
     def __init__(self, name):
          # 需要被Mock的初始化逻辑
          pass

     def write(self, message):
          # 需要被Mock为返回None的方法
          pass

解决方案

核心思路

打补丁的关键是针对目标被引用的位置,而非它的定义位置。对于全局作用域初始化的log实例,有两种处理方式:要么在模块导入前完成补丁,让全局实例直接是Mock对象;要么在模块导入后,单独Mock已存在的全局实例的方法。

方案一:Mock模块中的Logger类(覆盖全局与函数内实例)

通过@patch直接Mock目标模块中导入的Logger类,同时处理已初始化的全局log实例,实现全场景Mock。

示例测试代码(假设测试文件为tests/test_main.py):

from unittest.mock import patch, MagicMock
import src.apis.main as main_module
import src.apis.routers.routes as routes_module

def test_main_and_route_flow():
    # 分别Mock两个模块中的Logger类
    with patch('src.apis.main.Logger') as mock_main_logger, \
         patch('src.apis.routers.routes.Logger') as mock_route_logger:
        
        # 配置Mock实例的行为:__init__不做实际操作,write返回None
        mock_main_instance = MagicMock()
        mock_main_instance.write.return_value = None
        mock_main_logger.return_value = mock_main_instance

        mock_route_instance = MagicMock()
        mock_route_instance.write.return_value = None
        mock_route_logger.return_value = mock_route_instance

        # 由于模块已提前导入,全局的log实例是原始Logger对象,需要单独Mock它的write方法
        main_module.log.write = MagicMock(return_value=None)
        routes_module.log.write = MagicMock(return_value=None)

        # 执行测试逻辑
        main_module.main()

        # 验证全局作用域的write调用
        main_module.log.write.assert_called_once_with("logger started")
        routes_module.log.write.assert_any_call(message="Inside route")

        # 验证函数内创建的Logger实例的write调用
        mock_main_instance.write.assert_called_once_with("inside main")
        # 验证route函数内的write调用
        routes_module.log.write.assert_any_call(message="Logging done.")

方案二:提前补丁模块(避免全局实例提前初始化)

如果模块在导入阶段就执行了全局的log.write,可以在测试文件开头先打补丁,确保全局实例创建时直接是Mock对象。

from unittest.mock import patch, MagicMock

# 在导入目标模块前,先对main.py的Logger打补丁
with patch('src.apis.main.Logger') as mock_main_logger:
    mock_main_instance = MagicMock()
    mock_main_instance.write.return_value = None
    mock_main_logger.return_value = mock_main_instance
    import src.apis.main as main_module

# 同样提前对routes.py的Logger打补丁
with patch('src.apis.routers.routes.Logger') as mock_route_logger:
    mock_route_instance = MagicMock()
    mock_route_instance.write.return_value = None
    mock_route_logger.return_value = mock_route_instance
    import src.apis.routers.routes as routes_module

def test_full_log_flow():
    # 执行测试逻辑
    main_module.main()

    # 验证所有write调用
    mock_main_instance.write.assert_any_call("logger started")
    mock_main_instance.write.assert_any_call("inside main")
    mock_route_instance.write.assert_any_call(message="Inside route")
    mock_route_instance.write.assert_any_call(message="Logging done.")

关键注意事项

  • 补丁路径必须准确:必须使用目标模块中导入Logger的路径(如src.apis.main.Logger),而非Logger的定义路径(utils.log.logging.Logger)。
  • 全局实例的特殊处理:若模块已提前导入,全局log实例是原始对象,需单独Mock其方法;若在导入前打补丁,全局实例会直接是Mock对象。
  • 统一Mock行为:可以复用同一个MagicMock实例,统一两个模块的Logger行为,减少重复代码。

内容的提问来源于stack exchange,提问作者Amogh Mishra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 23:50:29