目录
pytest 会自动捕获 WARNING 级别及以上(包括 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 =====================
默认情况下,每个捕获的日志消息都会显示模块名、行号、日志级别和消息内容。
如果需要,可以通过传递特定的格式化选项来指定日志和日期格式,这些选项是日志记录模块所支持的:
pytest --log-format="%(asctime)s %(levelname)s %(message)s" \
--log-date-format="%Y-%m-%d %H:%M:%S"
命令中的 --log-format
选项用于定义日志消息的格式,而 --log-date-format
选项则用于定义日期和时间的格式。%(asctime)s
会被替换为日志事件发生的时间(根据 --log-date-format
指定的格式),%(levelname)s
会被替换为日志级别(如 DEBUG, INFO, WARNING, ERROR, CRITICAL),%(message)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 =====================
这些选项也可以通过 pytest.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
main
和 testing
这两个日志记录器的日志输出将被禁用,即 pytest 将不会捕获和显示这两个记录器产生的日志消息。这对于减少测试输出中的噪音或专注于特定日志记录器的输出非常有用。
此外,完全可以通过以下命令来禁用对失败测试中捕获内容(stdout、stderr 和日志)的报告:
pytest --show-capture=no
caplog夹具
caplog
fixture 在测试内部允许你更改捕获的日志消息的日志级别。
import logging
def test_foo(caplog):
caplog.set_level(logging.INFO)
# 接下来是你的测试代码,此时只会捕获 INFO 级别及以上的日志消息
# ...(测试逻辑)
caplog.set_level(logging.INFO)
调用将捕获的日志级别设置为 INFO
。这意味着,只有 INFO
、WARNING
、ERROR
和 CRITICAL
级别的日志消息会被 pytest 捕获并可能显示在测试报告中(取决于其他 pytest 配置和选项)。如果你希望在测试中看到更详细的日志(比如 DEBUG
级别的日志),你可以将日志级别相应地设置为 logging.DEBUG
。
默认情况下,日志级别是设置在根日志记录器(root logger)上的,但是为了方便起见,也可以设置任何日志记录器的日志级别。这可以通过在 caplog.set_level()
方法中指定 logger
参数来实现:
def test_foo(caplog):
caplog.set_level(logging.CRITICAL, logger="root.baz")
# 接下来是你的测试代码,此时只会捕获 'root.baz' 日志记录器下 CRITICAL 级别及以上的日志消息
# ...(测试逻辑)
caplog.set_level(logging.CRITICAL, logger="root.baz")
调用将 root.baz
日志记录器的日志级别设置为 CRITICAL
。只有 CRITICAL
级别的日志消息从 root.baz
日志记录器(或其子记录器)发出时,才会被 pytest 捕获并可能显示在测试报告中。
重要的是要注意,在测试结束时,caplog
fixture 会自动恢复所有被修改的日志记录器的日志级别。
你可以使用上下文管理器来临时更改 with
块内部的日志级别。这对于需要在测试中的特定部分改变日志级别,而在块外部保持原有级别的情况非常有用。caplog
fixture 中at_level
方法就是这样一个上下文管理器。
def test_bar(caplog):
with caplog.at_level(logging.INFO):
# 在这个 with 块内部,捕获的日志级别被设置为 INFO
# 因此,INFO 级别及以上的日志消息将被捕获
# ...(测试逻辑,可能会产生日志消息)
pass
# 退出 with 块后,日志级别将恢复到之前的设置
# 如果之前没有显式设置,则可能是 pytest 的默认设置或根日志记录器的设置
再次强调,默认情况下,caplog.at_level()
上下文管理器会影响根日志记录器(root logger)的级别,但你也可以通过指定 logger
参数来更改任何日志记录器的级别。
def test_bar(caplog):
with caplog.at_level(logging.CRITICAL, logger="root.baz"):
# 在这个 with 块内部,'root.baz' 日志记录器的捕获级别被设置为 CRITICAL
# 因此,只有 CRITICAL 级别的日志消息从 'root.baz' 或其子记录器发出时才会被捕获
# ...(测试逻辑,可能会产生日志消息)
pass
# 退出 with 块后,'root.baz' 日志记录器的捕获级别将恢复到之前的状态
# 这不会影响其他日志记录器的级别
最后,测试运行期间发送到日志记录器的所有日志都会以 logging.LogRecord
实例和最终的日志文本形式在 fixture 上可用。这对于当你想要断言消息内容时非常有用。以下是一个示例,展示了如何在测试中使用 caplog
来检查日志记录:
def test_baz(caplog):
# 调用被测试的函数,该函数可能会产生日志消息
func_under_test()
# 遍历捕获的日志记录
for record in caplog.records:
# 断言日志记录的级别名称不是 "CRITICAL"
assert record.levelname != "CRITICAL"
# 断言最终的日志文本中不包含 "wally"
assert "wally" not in caplog.text
caplog.records
是一个包含所有捕获的 logging.LogRecord
实例的列表。每个 LogRecord
实例都包含了日志消息的详细信息,如级别(levelname
)、消息文本(msg
)、时间戳(created
)等。通过遍历这个列表,你可以对日志记录进行详细的断言,比如检查日志的级别是否符合预期。
caplog.text
则是捕获的所有日志消息连接成一个字符串后的结果,这对于检查日志文本中是否包含或不包含特定字符串很有用。
要查看日志记录的所有可用属性,请查阅 logging.LogRecord 类的文档。
如果你只是想确保在给定日志记录器名称下,以给定的严重性和消息记录了某些消息,你可以使用 record_tuples
属性来简化断言。record_tuples
提供了一个元组的列表,每个元组包含三个元素:日志记录器的名称、日志级别(作为整数)和日志消息(格式化后的字符串)。
def test_foo(caplog):
# 记录一条日志消息
logging.getLogger().info("boo %s", "arg")
# 断言记录的日志元组与预期匹配
# 注意:日志消息中的占位符已被实际参数替换
assert caplog.record_tuples == [("root", logging.INFO, "boo arg")]
logging.getLogger().info("boo %s", "arg")
调用会在根日志记录器(默认情况下,如果不指定名称)下记录一条 INFO
级别的日志消息,消息内容为 "boo arg"
(注意,字符串格式化已经发生)。然后,assert
语句使用 caplog.record_tuples
来验证是否确实记录了预期的日志消息。
你可以调用 caplog.clear()
来在测试中重置捕获的日志记录。这对于在测试的不同阶段之间清除日志记录非常有用,以确保你只对当前阶段的日志进行断言。
def test_something_with_clearing_records(caplog):
# 调用一个生成日志记录的方法
some_method_that_creates_log_records()
# 清除当前捕获的所有日志记录
caplog.clear()
# 调用你的测试方法,该方法可能会产生新的日志记录
your_test_method()
# 现在,caplog.records 只包含 your_test_method() 生成的日志记录
# 你可以对这些记录进行断言
assert ["Foo"] == [rec.message for rec in caplog.records] # 假设 "Foo" 是预期的日志消息
需要注意的是,caplog.records
属性只包含当前阶段(即当前测试函数或方法)的日志记录。在测试的设置(setup)阶段,它只包含设置日志;在调用(call)阶段,它包含测试方法本身生成的日志;在拆卸(teardown)阶段,它则包含拆卸日志。因此,在调用 caplog.clear()
后,所有之前的日志记录都将被清除,caplog.records
将只包含之后生成的日志记录。这使得 caplog.clear()
成为在测试的不同部分之间隔离日志记录的有效工具。
为了访问其他阶段(如设置、调用和拆卸阶段)的日志,你可以用 caplog.get_records(when)
方法。这个方法允许你根据日志记录生成的阶段('setup'
, 'call'
, 'teardown'
)来检索日志记录。
例如,如果你想确保使用特定fixture的测试从不记录任何警告,你可以在拆除期间检查设置和调用阶段的记录,如下所示:
import pytest
import logging
@pytest.fixture
def window(caplog):
# 假设 create_window 是一个创建窗口的函数,它可能在创建过程中记录日志
window = create_window()
# 在 yield 语句之后,控制权会返回到测试函数,测试函数执行完毕后回到这里
yield window
# 在拆卸阶段,检查设置和调用阶段的日志记录
for when in ("setup", "call"):
# 使用 caplog.get_records(when) 获取指定阶段的日志记录
# 然后过滤出级别为 WARNING 的记录
messages = [
x.message for x in caplog.get_records(when) if x.levelno == logging.WARNING
]
# 如果存在警告消息,则使用 pytest.fail 使测试失败
if messages:
pytest.fail(f"warning messages encountered during {when}: {messages}")
# 注意:这里假设 create_window 是一个你已经定义的函数
# 它可能会在创建窗口的过程中记录日志
def create_window():
# 模拟创建窗口的过程,可能包括记录日志
logging.warning("This is a warning message during window creation")
return "a window"
# 示例测试函数,使用 window fixture
def test_window_no_warnings(window):
# 测试逻辑...
pass
# 注意:由于 create_window 函数在示例中直接记录了警告,
# 因此 test_window_no_warnings 测试将失败,
# 除非 create_window 函数的实现被修改以避免记录警告。
完整的 API 可以在 pytest.LogCaptureFixture. 中找到。
警告
caplog
fixture 会向根日志记录器添加一个处理器以捕获日志。如果在测试过程中修改了根日志记录器,例如使用 logging.config.dictConfig
,那么这个处理器可能会被移除,从而导致无法捕获任何日志。为了避免这种情况,请确保对根日志记录器的任何配置都只会向现有的处理器添加内容,而不是替换或移除它们。
实时日志
通过设置 log_cli 配置选项为 true
,pytest 将在日志记录被发送到控制台时直接输出这些日志记录。
你可以通过传递 --log-cli-level
参数来指定控制台输出日志记录的级别,这样控制台就会打印出等于或高于该级别的日志记录。这个设置接受日志级别名称或日志文档logging’s documentation.中看到的数值。
此外,你还可以指定 --log-cli-format
和 --log-cli-date-format
参数,它们在没有提供时分别默认为 --log-format
和 --log-date-format
的值,但仅应用于控制台日志处理器。这意味着你可以为控制台输出定制日志的格式和日期格式,而不影响其他日志处理器(如文件日志处理器)的格式设置。
所有的命令行界面(CLI)日志选项也可以在配置INI文件中设置。这些选项的名称分别是:
log_cli_level
log_cli_format
log_cli_date_format
如果你需要将整个测试套件的日志调用记录到文件中,你可以通过 --log-file=/path/to/log/file
参数来指定日志文件的路径。这个日志文件默认以写入模式打开,这意味着每次运行测试会话时,它都会被覆盖。如果你希望文件以追加模式打开,那么你可以传递 --log-file-mode=a
参数。请注意,日志文件位置的相对路径,无论是通过命令行传递还是在配置文件中声明的,它们总是相对于当前工作目录来解析的。
你还可以通过传递 --log-file-level
参数来指定日志文件的日志级别。这个设置接受日志级别名称或数值可以在日志文档 logging’s documentation中看到。
此外,你还可以指定 --log-file-format
和 --log-file-date-format
参数,它们分别等价于 --log-format
和 --log-date-format
,但仅应用于日志文件日志处理器。这意味着你可以为日志文件定制不同的日志格式和日期格式,而这些格式不会影响到控制台或其他日志处理器的输出。
所有的日志文件选项也可以在配置INI文件中设置。这些选项的名称是:
log_file
log_file_mode
log_file_level
log_file_format
log_file_date_format
你可以调用 set_log_path()
函数来动态地自定义 log_file
路径。但是,请注意,这一功能被认为是实验性的,可能在未来版本中发生变化。另外,set_log_path()
函数会尊重 log_file_mode
选项的设置,即如果 log_file_mode
设置为追加模式('a'),则使用 set_log_path()
设置的路径时也会以追加模式写入日志文件。
自定义颜色
如果启用了彩色终端输出,则日志级别会以彩色显示。通过 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,你可以通过在pytest.ini文件中添加以下内容来禁用内部功能:
[pytest]
addopts=-p no:logging
这样,pytest在运行时就不会加载和使用其内置的日志捕获功能,而是会回退到使用pytest-catchlog或其他配置的日志处理方式(如果有的话)。
pytest 3.4 中的不兼容变更
此功能在 3.3 版本中引入,并根据社区反馈在 3.4 版本中进行了一些不兼容的变更:
-
除非通过
log_level
配置或--log-level
命令行选项明确请求,否则日志级别将不再被更改。这允许用户自己配置日志记录器对象。设置log_level
将设置全局捕获的级别,因此如果某个特定测试需要比此更低的级别,请使用caplog.set_level()
功能,否则该测试可能会失败。 -
实时日志现在默认是禁用的,可以通过将
log_cli
配置选项设置为true
来启用。启用后,将增加详细程度,以便为每个测试显示日志。 -
实时日志现在发送到
sys.stdout
,并且不再需要-s
命令行选项即可工作。这意味着即使启用了捕获标准输出(通常用于捕获和显示测试的输出),实时日志也会直接显示在控制台上。
如果你想要部分恢复 3.3 版本的日志记录行为,你可以在你的 ini 文件中添加以下选项:
[pytest]
log_cli=true
log_level=NOTSET
这些更改背后的讨论详情可以在 #3013.中查看。