如何使用pytest和unittest测试Python特定日志格式
解决pytest测试中验证自定义日志格式的问题
问题核心是pytest会默认替换日志配置,且assertLogs捕获的是经过pytest格式化后的日志输出,而非应用本身的格式结果。要验证自定义格式,需直接捕获原始日志记录,再用我们的格式器重新格式化后校验。
解决方案步骤
- 避开pytest的日志处理器,直接获取日志的原始
LogRecord对象 - 使用我们定义的格式器对原始记录进行格式化
- 校验格式化后的字符串是否符合预期格式
修改后的测试代码
替换原测试部分为以下代码:
import unittest import logging from logging import LogRecord # 复用之前定义的日志格式 LOG_CONFIG_FORMAT = '%(asctime)s {%(process)d-%(thread)d} %(levelname)s \ %(name)s@%(lineno)d my_field_1=%(my_field_1)s my_field_2=%(my_field_2)s my_field_3=\ %(my_field_3)s: %(message)s ' class TestCase(unittest.TestCase): def test_log_format(self): # 创建格式器,使用我们的自定义格式 formatter = logging.Formatter(LOG_CONFIG_FORMAT) captured_records = [] # 定义一个自定义处理器,用于捕获原始LogRecord class CaptureHandler(logging.Handler): def emit(self, record: LogRecord) -> None: captured_records.append(record) # 获取应用的logger,添加自定义处理器 app_logger = logging.getLogger(__name__) capture_handler = CaptureHandler() app_logger.addHandler(capture_handler) try: # 执行被测函数 my_func() # 对每个捕获的记录进行格式化,然后校验 for record in captured_records: # LoggerAdapter的extra会合并到record的__dict__中,可直接格式化 formatted_log = formatter.format(record) # 校验格式中的必填字段是否存在 self.assertIn('my_field_1=', formatted_log) self.assertIn('my_field_2=', formatted_log) self.assertIn('my_field_3=', formatted_log) # 校验特定场景下的字段值(比如i=2时的字段) if record.message == '2': self.assertIn('my_field_1=BUST!!!', formatted_log) self.assertIn('my_field_2=BOOM!!!', formatted_log) self.assertIn('my_field_3=CRASH!!!', formatted_log) finally: # 移除处理器,避免影响其他测试 app_logger.removeHandler(capture_handler)
关键说明
- 捕获原始记录:通过自定义
CaptureHandler直接获取LogRecord,这是日志未被任何格式器处理的原始数据,包含所有我们设置的extra字段。 - 手动格式化:用我们自己的
Formatter对记录进行格式化,确保得到的是应用实际会输出的格式字符串。 - 清理资源:测试完成后移除自定义处理器,防止对其他测试用例造成干扰。
额外优化(可选)
如果不想在每个测试中重复编写处理器逻辑,可以把捕获日志的逻辑封装成一个上下文管理器:
from contextlib import contextmanager @contextmanager def capture_logs(logger_name): logger = logging.getLogger(logger_name) captured = [] handler = logging.Handler() handler.emit = lambda r: captured.append(r) logger.addHandler(handler) try: yield captured finally: logger.removeHandler(handler)
然后在测试中简化使用:
def test_log_format(self): formatter = logging.Formatter(LOG_CONFIG_FORMAT) with capture_logs(__name__) as captured_records: my_func() for record in captured_records: formatted = formatter.format(record) # 执行校验逻辑...
内容的提问来源于stack exchange,提问作者cyau
相关产品推荐
相关产品推荐

