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

如何Mock含非简单对象的logging.config.dictConfig配置字典?

解决logging配置测试中StringIO实例无法比较的问题

问题描述

测试setup_logging函数时,直接使用assert_called_once_with(expected_conf_dict)会失败,原因是io.StringIO未实现__eq__方法——即使两个StringIO实例功能一致,也会被判定为不相等:

>>> s1=StringIO()
>>> s2=StringIO()
>>> assert s1==s2
AssertionError

下面提供三种可行的解决方案:


方案1:提取实际参数,逐字段针对性校验

通过mock对象的call_args获取传入dictConfig的真实配置,对普通字段直接比较相等性,对stream字段仅校验类型是否为StringIO。

修改后的测试代码:

from io import StringIO
from unittest import mock
from application.utils import setup_logging

@mock.patch("logging.config.dictConfig")
def test_setup_logging(mock_logging_config):
    setup_logging()
    
    # 获取实际传入的配置字典
    actual_config = mock_logging_config.call_args[0][0]
    
    # 定义预期配置(剔除stream的具体实例)
    expected_config = {
        "version": 1,
        "disable_existing_loggers": False,
        "incremental": False,
        "formatters": {
            "standard": {
                "format": "[%(threadName)s] %(asctime)s %(levelname)s %(name)s: %(message)s",
                "datefmt": "%Y-%m-%d %H:%M:%S",
            }
        },
        "handlers": {
            "console": {
                "level": "INFO",
                "formatter": "standard",
                "class": "logging.StreamHandler",
            }
        },
        "root": {"handlers": ["console"], "level": "INFO"},
        "loggers": {
            "py4j.java_gateway": {"level": "ERROR"},
            "botocore": {"level": "ERROR"},
            "boto3": {"level": "ERROR"},
            "s3transfer": {"level": "ERROR"},
            "urllib3": {"level": "ERROR"},
        },
    }
    
    # 复制实际配置并移除stream字段,与预期配置比较
    actual_config_copy = actual_config.copy()
    actual_console_handler = actual_config_copy["handlers"]["console"].copy()
    stream_instance = actual_console_handler.pop("stream")
    actual_config_copy["handlers"]["console"] = actual_console_handler
    
    assert actual_config_copy == expected_config
    
    # 单独验证stream的类型
    assert isinstance(stream_instance, StringIO)

方案2:Mock StringIO类,让实例支持相等比较

通过unittest.mock.patch替换原StringIO类,给mock实例添加__eq__方法,使其可以被比较。

修改后的测试代码:

from io import StringIO
from unittest import mock
from application.utils import setup_logging

@mock.patch("logging.config.dictConfig")
@mock.patch("application.utils.StringIO")
def test_setup_logging(mock_string_io, mock_logging_config):
    # 让mock的StringIO实例支持相等判断
    mock_stream = mock_string_io.return_value
    mock_stream.__eq__ = lambda self, other: isinstance(other, (mock.Mock, StringIO))
    
    expected_conf_dict = {
        "version": 1,
        "disable_existing_loggers": False,
        "incremental": False,
        "formatters": {
            "standard": {
                "format": "[%(threadName)s] %(asctime)s %(levelname)s %(name)s: %(message)s",
                "datefmt": "%Y-%m-%d %H:%M:%S",
            }
        },
        "handlers": {
            "console": {
                "level": "INFO",
                "formatter": "standard",
                "class": "logging.StreamHandler",
                "stream": mock_stream,
            }
        },
        "root": {"handlers": ["console"], "level": "INFO"},
        "loggers": {
            "py4j.java_gateway": {"level": "ERROR"},
            "botocore": {"level": "ERROR"},
            "boto3": {"level": "ERROR"},
            "s3transfer": {"level": "ERROR"},
            "urllib3": {"level": "ERROR"},
        },
    }

    setup_logging()
    mock_logging_config.assert_called_once_with(expected_conf_dict)

方案3:重构原代码,解耦stream依赖

将StringIO的创建逻辑抽离,允许函数接收自定义的stream参数,测试时传入可比较的mock对象即可。

修改后的原代码(application/utils.py)

import logging
from io import StringIO

def setup_logging(conf=None, stream=None):
    if conf is None:
        # 优先使用传入的stream,否则创建新的StringIO
        log_stream = stream if stream is not None else StringIO()
        config = {
            "version": 1,
            "disable_existing_loggers": False,
            "incremental": False,
            "formatters": {
                "standard": {
                    "format": "[%(threadName)s] %(asctime)s %(levelname)s %(name)s: %(message)s",
                    "datefmt": "%Y-%m-%d %H:%M:%S",
                }
            },
            "handlers": {
                "console": {
                    "level": "INFO",
                    "formatter": "standard",
                    "class": "logging.StreamHandler",
                    "stream": log_stream,
                }
            },
            "root": {"handlers": ["console"], "level": "INFO"},
            "loggers": {
                "py4j.java_gateway": {"level": "ERROR"},
                "botocore": {"level": "ERROR"},
                "boto3": {"level": "ERROR"},
                "s3transfer": {"level": "ERROR"},
                "urllib3": {"level": "ERROR"},
            },
        }
        logging.config.dictConfig(config)
    else:
        # 基于配置文件读取的相关代码...

对应的测试代码

from unittest import mock
from application.utils import setup_logging

@mock.patch("logging.config.dictConfig")
def test_setup_logging(mock_logging_config):
    # 创建可比较的mock stream
    mock_stream = mock.Mock()
    
    expected_conf_dict = {
        "version": 1,
        "disable_existing_loggers": False,
        "incremental": False,
        "formatters": {
            "standard": {
                "format": "[%(threadName)s] %(asctime)s %(levelname)s %(name)s: %(message)s",
                "datefmt": "%Y-%m-%d %H:%M:%S",
            }
        },
        "handlers": {
            "console": {
                "level": "INFO",
                "formatter": "standard",
                "class": "logging.StreamHandler",
                "stream": mock_stream,
            }
        },
        "root": {"handlers": ["console"], "level": "INFO"},
        "loggers": {
            "py4j.java_gateway": {"level": "ERROR"},
            "botocore": {"level": "ERROR"},
            "boto3": {"level": "ERROR"},
            "s3transfer": {"level": "ERROR"},
            "urllib3": {"level": "ERROR"},
        },
    }

    setup_logging(stream=mock_stream)
    mock_logging_config.assert_called_once_with(expected_conf_dict)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 02:42:03