pytest 日志管理:捕获、断言与实时输出

版本与验证范围:本文依据 2026-10-02 核验留存的官方 stable 文档快照;缓存页面没有明确标注精确的 pytest 发布版本。文中的 pytest 3.3、3.4 属于历史行为变更说明,不代表本文要求使用这些旧版本;实验性 API 的限制按原文保留。配置片段保留原文的 TOML 与 INI 两种写法,使用前需按目标 pytest 版本核对配置文件格式。本站未运行或实测文中的命令与示例,代码和示例输出均逐字保留自原文,不代表本站运行结果。

pytest 会自动捕获 WARNING 及以上级别的日志消息,并在每个失败测试的报告中,用独立的区域显示这些日志,方式与捕获的 stdout 和 stderr 相同。

不带任何选项运行:

pytest

失败测试会显示为:

----------------------- Captured stdlog call ----------------------
test_reporting.py    26 WARNING  text going to logger
----------------------- Captured stdout call ----------------------
text going to stdout
----------------------- Captured stderr call ----------------------
text going to stderr
==================== 2 failed in 0.02 seconds =====================

默认情况下,每条捕获的日志消息都会显示模块、行号、日志级别和消息内容。

如果需要,可以通过传入相应的格式选项,将日志格式和日期格式设置为 logging 模块支持的任意格式:

pytest --log-format="%(asctime)s %(levelname)s %(message)s" \
        --log-date-format="%Y-%m-%d %H:%M:%S"

失败测试会显示为:

----------------------- Captured stdlog call ----------------------
2010-04-10 14:48:44 WARNING text going to logger
----------------------- Captured stdout call ----------------------
text going to stdout
----------------------- Captured stderr call ----------------------
text going to stderr
==================== 2 failed in 0.02 seconds =====================

这些选项也可以通过配置文件进行自定义:

TOML 配置示例(原文)

[pytest]
log_format = "%(asctime)s %(levelname)s %(message)s"
log_date_format = "%Y-%m-%d %H:%M:%S"

INI 配置示例(原文)

[pytest]
log_format = %(asctime)s %(levelname)s %(message)s
log_date_format = %Y-%m-%d %H:%M:%S

可以通过 --log-disable={logger_name} 禁用指定的日志记录器。这个参数可以传入多次:

pytest --log-disable=main --log-disable=testing

此外,还可以使用以下选项,完全关闭失败测试报告中对已捕获内容的展示,包括 stdout、stderr 和日志:

pytest --show-capture=no

caplog 测试夹具

在测试内部,可以更改所捕获日志消息的日志级别。caplog fixture(测试夹具)提供了这一能力:

def test_foo(caplog):
    caplog.set_level(logging.INFO)

默认情况下,设置的是根日志记录器的级别。为方便使用,也可以设置任意日志记录器的日志级别:

def test_foo(caplog):
    caplog.set_level(logging.CRITICAL, logger="root.baz")

这样设置的日志级别会在测试结束时自动恢复。

也可以使用上下文管理器,在 with 代码块内临时更改日志级别:

def test_bar(caplog):
    with caplog.at_level(logging.INFO):
        pass

同样,默认情况下受影响的是根日志记录器的级别;也可以改为更改任意其他日志记录器的级别:

def test_bar(caplog):
    with caplog.at_level(logging.CRITICAL, logger="root.baz"):
        pass

最后,测试运行期间发送给日志记录器的所有日志,都可以通过这个 fixture 访问,既包括 logging.LogRecord 实例,也包括最终的日志文本。如果需要对消息内容进行断言,这会很有用:

def test_baz(caplog):
    func_under_test()
    for record in caplog.records:
        assert record.levelname != "CRITICAL"
    assert "wally" not in caplog.text

日志记录的所有可用属性,请参阅 logging.LogRecord 类。

如果只需要确认某些消息确实以指定的日志记录器名称、严重程度和消息内容被记录,也可以使用 record_tuples:

def test_foo(caplog):
    logging.getLogger().info("boo %s", "arg")

    assert caplog.record_tuples == [("root", logging.INFO, "boo arg")]

可以调用 caplog.clear(),清空测试中已经捕获的日志记录:

def test_something_with_clearing_records(caplog):
    some_method_that_creates_log_records()
    caplog.clear()
    your_test_method()
    assert ["Foo"] == [rec.message for rec in caplog.records]

caplog.records 属性只包含当前阶段的记录。因此,在 setup 阶段中,它只包含 setup 日志;call 和 teardown 阶段也是如此。

要访问其他阶段的日志,请使用 caplog.get_records(when) 方法。例如,如果要确保使用某个 fixture 的测试不会记录任何警告,可以在 teardown 期间,检查 setup 和 call 阶段的日志记录:

@pytest.fixture
def window(caplog):
    window = create_window()
    yield window
    for when in ("setup", "call"):
        messages = [
            x.message for x in caplog.get_records(when) if x.levelno == logging.WARNING
        ]
        if messages:
            pytest.fail(f"warning messages encountered during testing: {messages}")

完整的 API 见 pytest.LogCaptureFixture。

警告

caplog fixture 会向根日志记录器添加一个处理器,以便捕获日志。如果在测试期间修改根日志记录器,例如使用 logging.config.dictConfig,这个处理器可能会被移除,导致无法捕获任何日志。为避免这种情况,应确保对根日志记录器的任何配置,都只在现有处理器的基础上添加处理器。

实时日志

将配置选项 log_cli 设置为 true 后,pytest 会在日志记录产生时,直接将其输出到控制台。

可以通过传入 --log-cli-level 指定日志级别,级别等于或高于该值的日志记录会打印到控制台。这个设置接受 logging 文档中列出的日志级别名称或数值。

此外,还可以指定 --log-cli-format 和 --log-cli-date-format。这两个选项分别对应 --log-format 和 --log-date-format,未提供时会默认使用后两者的值,但它们只作用于控制台日志处理器。

所有 CLI 日志选项也都可以在配置文件中设置。选项名称如下:

如果需要将整个测试套件的日志调用记录到文件,可以传入 --log-file=/path/to/log/file。默认情况下,这个日志文件以写入模式打开,这意味着每次测试会话都会覆盖它。如果希望改为以追加模式打开文件,可以传入 --log-file-mode=a。请注意,无论日志文件的位置是通过 CLI 传入,还是在配置文件中声明,其中的相对路径始终相对于当前工作目录进行解析。

也可以通过传入 --log-file-level 指定日志文件的日志级别。这个设置接受 logging 文档中列出的日志级别名称或数值。

此外,还可以指定 --log-file-format 和 --log-file-date-format。这两个选项分别与 --log-format 和 --log-date-format 相同,但作用于日志文件的日志处理器。

所有日志文件选项也都可以在配置文件中设置。选项名称如下:

可以调用 set_log_path() 动态自定义 log_file 路径。这项功能被视为实验性功能。请注意,set_log_path() 会遵循 log_file_mode 选项。

自定义颜色

启用彩色终端输出后,日志级别会以不同颜色显示。通过 add_color_level(),可以修改默认颜色,也可以为自定义日志级别设置颜色。例如:

@pytest.hookimpl(trylast=True)
def pytest_configure(config):
    logging_plugin = config.pluginmanager.get_plugin("logging-plugin")

    # Change color on existing log level
    logging_plugin.log_cli_handler.formatter.add_color_level(logging.INFO, "cyan")

    # Add color to a custom log level (a custom log level `SPAM` is already set up)
    logging_plugin.log_cli_handler.formatter.add_color_level(logging.SPAM, "blue")

警告

这项功能及其 API 被视为实验性功能,可能在不同版本之间发生变化,且不会预先发出弃用通知。

版本说明

这项功能引入时,是作为 pytest-catchlog 插件的直接替代品,两者相互冲突。对 pytest-capturelog 的向后兼容 API 在引入这项功能时已被移除。因此,如果出于这一原因仍需要 pytest-catchlog,可以在配置文件中添加以下内容,禁用内置功能:

TOML 配置示例(原文)

[pytest]
addopts = ["-p", "no:logging"]

INI 配置示例(原文)

[pytest]
addopts = -p no:logging

pytest 3.4 中的不兼容变更

这项功能是在 3.3 中引入的。根据社区反馈,3.4 中进行了若干不兼容变更:

  • 除非通过配置选项 log_level 或命令行选项 --log-level 明确要求,否则不再更改日志级别。这样,用户可以自行配置日志记录器对象。设置 log_level 会指定全局捕获的日志级别。因此,如果某个测试需要比该级别更低的日志,请使用 caplog.set_level();否则,这个测试就容易失败。

  • 实时日志现在默认禁用,可以通过将配置选项 log_cli 设置为 true 来启用。启用后,输出详细程度会提高,以便看到每个测试的日志。

  • 实时日志现在发送到 sys.stdout,不再需要命令行选项 -s 才能工作。

如果想部分恢复 3.3 版本的日志行为,可以在配置文件中添加以下选项:

TOML 配置示例(原文)

[pytest]
log_cli = true
log_level = "NOTSET"

INI 配置示例(原文)

[pytest]
log_cli = true
log_level = NOTSET

有关促成这些变更的讨论,更多细节见 #3013。

原文版权与许可:Copyright © 2015, holger krekel and pytest-dev team。原页提供的 License(许可证) 入口予以保留。本页为中文译文;具体许可条款以项目原始许可证为准。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容