Python assertLogs 捕获阈值:代码写了日志,为什么退出 with 时测试仍失败
测试给一段处理流程加上 assertLogs,设置最低级别为 WARNING。流程确实调用了 logger.info,退出 with 时却报告没有捕获日志。这里需要验证的是“指定来源中至少有一条记录达到阈值”,不是“代码曾调用任意日志方法”。只检查控制台有没有字,也无法替代这个条件。
下面用独立名称的父、子 logger,把记录内容和缺失情况一起验收。完整代码保存为 demo.py 后运行 python demo.py,只用标准库且不写日志文件。为了让单文件演示直接执行,代码创建 TestCase 实例调用断言;放进项目测试类时,同样的断言可改为 self 上的方法。
AI生成概念插图:阈值上方的两个圆点进入收集盒,下方圆点留在外面,表示只捕获达到最低级别的记录;不是软件界面或运行截图。
import logging
import unittest
case = unittest.TestCase()
logger = logging.getLogger("batch26.demo")
child = logging.getLogger("batch26.demo.worker")
logger.setLevel(logging.DEBUG)
child.setLevel(logging.NOTSET)
child.propagate = True
with case.assertLogs(logger, level="WARNING") as captured:
logger.info("progress")
logger.warning("retry %s", 2)
child.error("stopped")
case.assertEqual([r.levelname for r in captured.records],
["WARNING", "ERROR"])
case.assertEqual([r.getMessage() for r in captured.records],
["retry 2", "stopped"])
case.assertEqual(captured.records[1].name, "batch26.demo.worker")
print("captured:", captured.output)
with case.assertRaises(AssertionError):
with case.assertLogs(logger, level="WARNING"):
logger.info("only progress")
print("no matching log: AssertionError")
with case.assertNoLogs(logger, level="WARNING"):
logger.info("ordinary progress")
print("no WARNING or higher: passed")级别是下限,不是精确匹配
第一段捕获 WARNING 和 ERROR 两条记录,INFO 没有进入结果。level="WARNING" 的意思是至少达到警告级别,因此更高的错误级别同样满足条件。若业务要求必须出现某条警告,仅仅让上下文成功还不够:一条不相关的错误也可能让“至少一条”这个条件通过。
代码因此继续检查 records 的级别、格式化消息与来源名称。records 保存 LogRecord 对象,适合按字段断言;output 是已经格式化的字符串列表,适合直接核对展示形式。带参数的日志用 getMessage 读取最终消息,才能得到 retry 2,而不是把原始格式模板和参数误当成已拼接的文字。
未命中在退出上下文时失败
第二段只发 INFO,没有任何记录满足 WARNING 阈值,所以退出 assertLogs 时产生 AssertionError。外层 assertRaises 是教学用来确认这个预期失败,不是在正式测试里掩盖缺失日志。真实业务若要求失败分支必须留下警告,就应让缺失直接导致测试失败,再查流程是否走到了那个分支。
相反,若目标是确认没有警告及更高级别记录,应使用 assertNoLogs。最后一段允许普通进度信息,因此通过。它并没有证明所有级别都完全安静,也没有返回供你读取的捕获对象。先把“至少出现”与“不得出现”写成清晰要求,再选择对应断言,避免把工具名字当成可互换的收集器。
指定父 logger 可以接住能够传播上来的子 logger 记录,所以例子明确把 worker 的传播打开,并使它继承有效级别。如果子 logger 自己禁止传播,或记录在产生前就被它的级别与过滤器挡住,父级的捕获不会凭空创造那条记录。找不到日志时,应沿真实来源和传播路径检查。
日志测试还应尽量缩小捕获范围,直接指定业务 logger,而不是为了方便总抓根 logger。宽范围可能收进其他库的输出,让无关消息替目标行为满足条件。并发测试也要留意共享日志配置。优先断言真正需要承诺的来源、级别和关键信息,避免把容易变化的时间戳或完整展示格式写死。
资料核对日期:2026年10月2日(北京时间)。示例在本地CPython 3.12.14实际运行并通过断言,结果对应文中固定输入。


