news 2026/9/14 21:31:13

pytest 3.5.0 版本特性全解析:新命令行选项、夹具作用域排序与 JUnit 日志集成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
pytest 3.5.0 版本特性全解析:新命令行选项、夹具作用域排序与 JUnit 日志集成

pytest 3.5.0 版本特性全解析:新命令行选项、夹具作用域排序与 JUnit 日志集成

【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest

导读

pytest 3.5.0 是 pytest 项目于 2018 年 3 月 21 日发布的一个重要版本,围绕"更可控的输出、更智能的测试排序、更灵活的收集控制"三大方向引入了大量新能力:包括--show-capture--new-first--deselect--verbosity等一批新命令行选项,调整了夹具(fixture)的实例化顺序,并让 JUnit XML 报告支持捕获日志输出。阅读本文后,你将掌握 3.5.0 引入的每个新选项的参数语义、对应的源码实现位置,以及如何在实际项目中组合使用它们来优化测试反馈与 CI 报告。

本文内容以仓库中的 3.5.0 版本记录 与 发布公告 为骨架,并深入 src/_pytest 下的核心实现进行源码级佐证。

一、版本速览:一次"质量与体验"并重的更新

发布公告指出,pytest 3.5.0 发布时项目已拥有超过 1600 个针对自身的测试用例,运行在多种解释器与平台上(见 发布公告)。该版本包含大量缺陷修复与功能改进,升级方式为:

pip install -U pytest

从版本记录(doc/en/changelog.rst#L7938-L8066)可以归纳出本次更新的四条主线:

  1. 命令行体验升级:新增--show-capture--rootdir--new-first--last-failed-no-failures--doctest-continue-on-failure--deselect--verbosity共 7 个新选项;
  2. 执行模型改进:夹具按作用域从高到低实例化,从机制上减少重复的 setup/teardown;
  3. 报告能力增强record_property通用化、JUnit XML 支持写入捕获日志;
  4. 内部重构mark.py变为包、verbosity 处理统一、与 argparse 深度集成等。

二、弃用与移除:两个重要信号

1.record_xml_property更名为record_property

record_xml_propertyfixture 被弃用,取而代之的是更通用的record_property(issue #2770)。旧名称仅作为兼容别名保留,新实现不再绑定 JUnit XML 格式,而是把属性写入测试报告,任何 reporter(含 xdist、marker 场景)都能读取。

在源码中,record_property的实现非常直观(src/_pytest/junitxml.py#L285-L304):

def record_property(request: FixtureRequest) -> Callable[[str, object], None]: """Add extra properties to the calling test. ... Example:: def test_function(record_property): record_property("example_key", 1) """ _warn_incompatibility_with_xunit2(request, "record_property") def append_property(name: str, value: object) -> None: request.node.user_properties.append((name, value)) return append_property

可以看到,新实现把(name, value)直接追加到request.node.user_properties,值会自动进行 XML 编码。这意味着属性数据与具体报告格式解耦,天然兼容 xdist 分布式执行。

2. 非顶层 conftest 中声明pytest_plugins被弃用

在非顶层 conftest.py 中定义pytest_plugins现在会触发弃用警告(issue #3084)。原因在于这类声明会"泄漏"(leak)到整个目录树,导致插件在预期外的目录下被加载,容易引发难以排查的副作用。最佳实践是把插件声明收敛到根目录的顶层 conftest.py。

三、新增命令行选项:逐项拆解

1.--show-capture:控制失败时捕获输出的展示方式

当测试失败时,pytest 默认会在终端回显捕获到的输出。--show-capture允许精确控制展示哪部分内容(issue #1478),取值如下:

取值含义
no失败时完全不显示任何捕获输出
stdout只显示捕获的标准输出
stderr只显示捕获的标准错误
log只显示捕获的日志
all全部显示(默认值)

实现位于 src/_pytest/terminal.py#L246-L254:

group.addoption( "--show-capture", action="store", dest="showcapture", choices=["no", "stdout", "stderr", "log", "all"], default="all", help="Controls how captured stdout/stderr/log is shown on failed tests. " "Default: all.", )

终端报告在打印Captured stdout/Captured stderr/Captured log各分段前会依据showcapture过滤:取值为no时不显示;取值为all时全显示;其他取值则要求分段名匹配指定内容(src/_pytest/terminal.py#L1189-L1193)。典型用法如 CI 中只关注日志、屏蔽海量 stdout:

pytest --show-capture=log

2.--rootdir:显式覆盖根目录探测规则

pytest 原本通过向上逐层寻找pytest.ini/pyproject.toml/tox.ini/setup.py等标记文件来确定 rootdir。--rootdir选项允许直接指定,绕过自动探测规则(issue #1642),在大型 monorepo 或多项目并存的场景下非常有用:

pytest --rootdir=/path/to/project

3.--nf/--new-first:让新测试先跑

--nf(即--new-first的长选项)会先运行新添加的测试文件,再运行其余测试,且两类测试内部都按文件修改时间(mtime)排序、更新近的文件优先(issue #3034)。该功能由NFPlugin实现(src/_pytest/cacheprovider.py#L445-L490):

class NFPlugin: """Plugin which implements the --nf (run new-first) option.""" def __init__(self, config: Config) -> None: self.config = config self.active = config.option.newfirst assert config.cache is not None self.cached_nodeids: set[NodeId] = { NodeId.parse(s) for s in config.cache.get("cache/nodeids", []) } @hookimpl(wrapper=True, tryfirst=True) def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> Generator[None]: res = yield if self.active: new_items: dict[NodeId, nodes.Item] = {} other_items: dict[NodeId, nodes.Item] = {} for item in items: if item.id not in self.cached_nodeids: new_items[item.id] = item else: other_items[item.id] = item items[:] = self._get_increasing_order( new_items.values() ) + self._get_increasing_order(other_items.values()) self.cached_nodeids.update(new_items) else: self.cached_nodeids.update(item.id for item in items) return res def _get_increasing_order(self, items: Iterable[nodes.Item]) -> list[nodes.Item]: return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True)

从源码可以看出核心机制:NFPlugin依赖缓存文件cache/nodeids记录"上次见过哪些测试",凡是本次新出现(不在缓存中)的 nodeid 归入new_items,其余归入other_items,再分别按st_mtime倒序拼接。每次会话结束时会把见过的 nodeid 回写缓存(src/_pytest/cacheprovider.py#L481-L490)。

# 新测试优先,适合开发迭代期快速验证新代码 pytest --new-first

4.--last-failed-no-failures:定义"上次无失败"时的行为

配合缓存插件的--lf/--last-failed使用,--last-failed-no-failures(短选项--lfnf)决定"上次运行没有任何失败(或没有缓存)"时执行什么(issue #3139):

  • all(默认):重新运行整个测试套件;
  • none:仅打印"没有已知失败"的消息并以成功状态退出,不执行任何测试。

选项定义见 src/_pytest/cacheprovider.py#L543-L555,其choices=("all", "none")、默认值为"all"

# 上次全绿时不再重跑,仅提示后成功退出 pytest --lf --last-failed-no-failures=none

5.--doctest-continue-on-failure:doctest 不因首个失败中断

默认情况下,doctest 遇到第一个失败片段即停止。加上该选项后,同一 snippet 内的多个失败会全部展示出来(issue #3149)。实现位于 src/_pytest/doctest.py#L113-L118,并在_get_continue_on_failure(src/_pytest/doctest.py#L411-L418)中读取配置:默认关闭,且当使用--pdb--pdbcls时会强制置为False(因为逐行进入调试器时继续执行没有意义)。

pytest --doctest-modules --doctest-continue-on-failure

6.--deselect:收集阶段按前缀批量剔除测试

--deselect允许在收集阶段直接剔除指定的测试(issue #3198),可多次传入。其实现采用"前缀匹配"语义(src/_pytest/main.py#L485-L498):

deselect_prefixes = tuple(config.getoption("deselect") or []) if not deselect_prefixes: return ... for colitem in items: if colitem.nodeid.startswith(deselect_prefixes): deselected.append(colitem) if deselected: config.hook.pytest_deselected(items=deselected)

即只要 nodeid 以某个前缀开头就被剔除,因此既能精确剔除单个测试,也能剔除某个文件或目录下的全部测试:

# 精确剔除单个测试 pytest --deselect tests/test_foo.py::test_bar # 按前缀剔除整个文件 pytest --deselect tests/test_slow.py

配合 src/_pytest/main.py#L178 中的选项注册,这一机制在同一版本中还带来了一个终端输出改进:被剔除的测试数量会在运行前显式展示,例如collected X items / Y deselected(issue #3213)。

7.--verbosity:显式设定详细程度

此前详细度只能通过叠加-v/-q间接控制,3.5.0 新增--verbosity直接指定数值(issue #3296),定义见 src/_pytest/terminal.py#L188-L194:

group.addoption( "--verbosity", dest="verbose", type=int, default=0, help="Set verbosity. Default: 0.", )

该选项落地时伴随着一次内部重构——"统一 verbosity 的内部处理方式",使-v-q--verbosity都落到同一个verbose配置项上,并为后续Config._add_verbosity_ini提供了统一的数值语义基础。

四、执行模型改进:夹具按作用域从高到低实例化

3.5.0 调整了夹具的实例化顺序(issue #2405):高作用域夹具(如session)先于低作用域夹具(如function)实例化,而同一作用域内的相对顺序保持不变——仍按声明顺序与依赖关系排列。

从源码看,这一保证由pytest_fixture_setup之前的闭包排序实现(src/_pytest/fixtures.py#L1980-L1995):

def sort_by_scope(arg_name: str) -> Scope: try: fixturedefs = arg2fixturedefs[arg_name] except KeyError: return Scope.Function else: return fixturedefs[-1]._scope fixturenames_closure = sorted( traverse_fixture_closure( initialnames, getfixturedefs=getfixturedefs, ), key=sort_by_scope, reverse=True, )

作用域值按Scope的枚举顺序排列(function < class < module < package < session),reverse=True后即得到"高作用域在前"的实例化顺序。这项改进的价值在于:session/module级夹具得以尽早初始化,其依赖的低作用域夹具也能在正确的上下文中创建,减少了因顺序不确定导致的重复 setup/teardown。

五、报告能力增强:JUnit XML 与日志的集成

1.junit_logging:把捕获日志写入 JUnit 报告

3.5.0 为junit_loggingini 选项赋予了实际能力(issue #3156):当值为system-out时,捕获的日志写入生成 XML 中的<system-out>标签;值为system-err时写入<system-err>;默认值no表示不写入。配置定义见 src/_pytest/junitxml.py#L407-L412:

parser.addini( "junit_logging", "Write captured log messages to JUnit report", type=_JunitLogging, default="no", )

典型配置(写入pytest.ini):

[pytest] junit_logging = system-out junit_log_passing_tests = true

配合--junitxml=report.xml即可让 CI 解析到每个用例的日志输出,对失败定位极为有效。

2. 日志插件与 live logging 的改进

  • 启用 live logs 时,日志插件现在能正确处理pytest_runtest_logstartpytest_runtest_logfinish钩子(issue #3189);
  • 命令行直接传--log-cli-level会自动激活 live logging,无需再手动--log-cli(issue #3190);
  • 进入 pdb 之前会先打印已捕获的日志(issue #3204),避免调试时缺少上下文。

六、标记表达式支持platform模块

pytest.mark的表达式求值环境新增了 Python 内置platform模块(issue #3236),使得基于平台的跳过/选择可以用更直白的方式表达,例如结合-m@pytest.mark.skipif

import pytest @pytest.mark.skipif("platform.system() == 'Windows'") def test_posix_only(): ...

从实现看,标记表达式通过受限环境求值(src/_pytest/mark/expression.py#L363):

return bool(eval(self._code, {"__builtins__": {}}, MatcherAdapter(matcher)))

platform作为内置模块注入该求值命名空间,从而在标记表达式字符串中可直接调用。

七、pytest.approx支持 numpy 数组与标量比较

pytest.approx现在可以直接用 numpy 数组与标量进行比较(issue #3312)。从 src/_pytest/approx.py 的实现看(如 approx.py#L219-L241 附近的_numpy_array处理逻辑),当期望值是 numpy 数组时,实际值既可以是形状匹配的数组,也可以是标量:

import numpy as np import pytest def test_numpy_scalar(): assert np.array([1.0, 2.0]) == pytest.approx(1.5, abs=0.5) # 数组 vs 标量

该特性同时修正了 numpy 比较运算的优先级问题(approx.py#L74 处注释说明通过__eq__与 numpy 集成),让数据科学场景下的断言写法更简洁。

八、缺陷修复要点

除新特性外,3.5.0 还修复了一批影响实际使用的问题:

  • Python 2.7 下关闭捕获临时文件时的IOError被抑制(issue #2370),避免偶尔出现的诡异报错;
  • caplog.clear()只清空了records而未清空text属性(issue #3297),该版本修复了二者不同步的问题;
  • 收集阶段DontReadFromStdin可迭代(issue #3314):当 stdin 不允许被读取时,DontReadFromStdin对象仍保持可迭代、可解析为迭代器而不崩溃,提升了在非交互环境(如某些 CI 与编辑器集成)下的健壮性。

九、内部重构与工程化变更

3.5.0 还包含一批对后续版本影响深远的内部改动(均见 版本记录):

  • attrs最低版本要求提升到17.4.0(issue #3228);pytest新增对more-itertools的依赖(issue #3265);
  • 内部mark.py模块重构为mark包(issue #3250),为后续标记体系扩展奠定结构基础;
  • 使用-c传入的.cfg文件若包含[pytest]段会给出警告(issue #3268);
  • FormattedExcinfo改用attrs设施并移除旧版 Python 支持代码(issue #3292);
  • verbosity 处理与 argparse 集成的两轮重构(issue #3296、#3304),正是--verbosity选项得以平滑落地的底层支撑;
  • FSCollectorNode构造函数现在接受显式传入的nodeids(issue #3291),为外部工具构建节点树提供了更清晰的接口。

结语

pytest 3.5.0 虽然是一个"小版本",却为后续数个版本的能力(缓存驱动的测试排序、细粒度输出控制、JUnit 日志集成、统一 verbosity 体系)打下了关键基础。对于开发者而言,本节最值得立即上手的三个能力是:用--show-capture=log收敛失败输出、用--new-first加速开发迭代反馈、用junit_logging让 CI 报告携带完整日志上下文。若想了解更完整的变更清单与逐条 issue 出处,可查阅仓库中的 doc/en/changelog.rst#L7938-L8066;发布公告原文见 doc/en/announce/release-3.5.0.rst。

【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 21:30:02

Copilot替代工具选型与部署指南:免费、付费、自托管全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 21:29:30

Java Stream处理大集合,我的内存怎么就炸了

上周压测时&#xff0c;我们的订单结算服务在峰值流量下OOM了。堆dump显示&#xff0c;一个本该分批处理的10万级订单集合&#xff0c;被整个塞进了Stream操作链——而这一切的罪魁祸首&#xff0c;竟然是一行看似无害的.stream().parallel()。 现象&#xff1a;并行流吃光了你…

作者头像 李华
网站建设 2026/9/14 21:29:25

JavaWeb小区物业管理系统:MVC分层、Servlet/JSP与MySQL实践解析

简介&#xff1a;基于JavaWeb的小区物业管理系统源代码与数据库&#xff0c;是一套面向计算机专业学生和JavaWeb初学者的课程设计/毕业设计项目资源。系统采用MVC架构&#xff0c;使用MySQL存储数据&#xff0c;前端基于BootStrap框架实现自适应界面&#xff0c;覆盖用户登录注…

作者头像 李华
网站建设 2026/9/14 21:28:57

Claude捐赠MCP协议:AI上下文管理的标准化革命

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华