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

发布时间:2026/9/14 21:31:18
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可以归纳出本次更新的四条主线命令行体验升级新增--show-capture、--rootdir、--new-first、--last-failed-no-failures、--doctest-continue-on-failure、--deselect、--verbosity共 7 个新选项执行模型改进夹具按作用域从高到低实例化从机制上减少重复的 setup/teardown报告能力增强record_property通用化、JUnit XML 支持写入捕获日志内部重构mark.py变为包、verbosity 处理统一、与 argparse 深度集成等。二、弃用与移除两个重要信号1.record_xml_property更名为record_propertyrecord_xml_propertyfixture 被弃用取而代之的是更通用的record_propertyissue #2770。旧名称仅作为兼容别名保留新实现不再绑定 JUnit XML 格式而是把属性写入测试报告任何 reporter含 xdist、marker 场景都能读取。在源码中record_property的实现非常直观src/_pytest/junitxml.py#L285-L304def 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-L254group.addoption( --show-capture, actionstore, destshowcapture, choices[no, stdout, stderr, log, all], defaultall, helpControls 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 中只关注日志、屏蔽海量 stdoutpytest --show-capturelog2.--rootdir显式覆盖根目录探测规则pytest 原本通过向上逐层寻找pytest.ini/pyproject.toml/tox.ini/setup.py等标记文件来确定 rootdir。--rootdir选项允许直接指定绕过自动探测规则issue #1642在大型 monorepo 或多项目并存的场景下非常有用pytest --rootdir/path/to/project3.--nf/--new-first让新测试先跑--nf即--new-first的长选项会先运行新添加的测试文件再运行其余测试且两类测试内部都按文件修改时间mtime排序、更新近的文件优先issue #3034。该功能由NFPlugin实现src/_pytest/cacheprovider.py#L445-L490class 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(wrapperTrue, tryfirstTrue) 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, keylambda item: item.path.stat().st_mtime, reverseTrue)从源码可以看出核心机制NFPlugin依赖缓存文件cache/nodeids记录上次见过哪些测试凡是本次新出现不在缓存中的 nodeid 归入new_items其余归入other_items再分别按st_mtime倒序拼接。每次会话结束时会把见过的 nodeid 回写缓存src/_pytest/cacheprovider.py#L481-L490。# 新测试优先适合开发迭代期快速验证新代码 pytest --new-first4.--last-failed-no-failures定义上次无失败时的行为配合缓存插件的--lf/--last-failed使用--last-failed-no-failures短选项--lfnf决定上次运行没有任何失败或没有缓存时执行什么issue #3139all默认重新运行整个测试套件none仅打印没有已知失败的消息并以成功状态退出不执行任何测试。选项定义见 src/_pytest/cacheprovider.py#L543-L555其choices(all, none)、默认值为all。# 上次全绿时不再重跑仅提示后成功退出 pytest --lf --last-failed-no-failuresnone5.--doctest-continue-on-failuredoctest 不因首个失败中断默认情况下doctest 遇到第一个失败片段即停止。加上该选项后同一 snippet 内的多个失败会全部展示出来issue #3149。实现位于 src/_pytest/doctest.py#L113-L118并在_get_continue_on_failuresrc/_pytest/doctest.py#L411-L418中读取配置默认关闭且当使用--pdb或--pdbcls时会强制置为False因为逐行进入调试器时继续执行没有意义。pytest --doctest-modules --doctest-continue-on-failure6.--deselect收集阶段按前缀批量剔除测试--deselect允许在收集阶段直接剔除指定的测试issue #3198可多次传入。其实现采用前缀匹配语义src/_pytest/main.py#L485-L498deselect_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(itemsdeselected)即只要 nodeid 以某个前缀开头就被剔除因此既能精确剔除单个测试也能剔除某个文件或目录下的全部测试# 精确剔除单个测试 pytest --deselect tests/test_foo.py::test_bar # 按前缀剔除整个文件 pytest --deselect tests/test_slow.py配合 src/_pytest/main.py#L178 中的选项注册这一机制在同一版本中还带来了一个终端输出改进被剔除的测试数量会在运行前显式展示例如collected X items / Y deselectedissue #3213。7.--verbosity显式设定详细程度此前详细度只能通过叠加-v/-q间接控制3.5.0 新增--verbosity直接指定数值issue #3296定义见 src/_pytest/terminal.py#L188-L194group.addoption( --verbosity, destverbose, typeint, default0, helpSet 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-L1995def 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, getfixturedefsgetfixturedefs, ), keysort_by_scope, reverseTrue, )作用域值按Scope的枚举顺序排列function class module package sessionreverseTrue后即得到高作用域在前的实例化顺序。这项改进的价值在于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-L412parser.addini( junit_logging, Write captured log messages to JUnit report, type_JunitLogging, defaultno, )典型配置写入pytest.ini[pytest] junit_logging system-out junit_log_passing_tests true配合--junitxmlreport.xml即可让 CI 解析到每个用例的日志输出对失败定位极为有效。2. 日志插件与 live logging 的改进启用 live logs 时日志插件现在能正确处理pytest_runtest_logstart与pytest_runtest_logfinish钩子issue #3189命令行直接传--log-cli-level会自动激活 live logging无需再手动--log-cliissue #3190进入 pdb 之前会先打印已捕获的日志issue #3204避免调试时缺少上下文。六、标记表达式支持platform模块pytest.mark的表达式求值环境新增了 Python 内置platform模块issue #3236使得基于平台的跳过/选择可以用更直白的方式表达例如结合-m或pytest.mark.skipifimport pytest pytest.mark.skipif(platform.system() Windows) def test_posix_only(): ...从实现看标记表达式通过受限环境求值src/_pytest/mark/expression.py#L363return 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, abs0.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.0issue #3228pytest新增对more-itertools的依赖issue #3265内部mark.py模块重构为mark包issue #3250为后续标记体系扩展奠定结构基础使用-c传入的.cfg文件若包含[pytest]段会给出警告issue #3268FormattedExcinfo改用attrs设施并移除旧版 Python 支持代码issue #3292verbosity 处理与 argparse 集成的两轮重构issue #3296、#3304正是--verbosity选项得以平滑落地的底层支撑FSCollector与Node构造函数现在接受显式传入的nodeidsissue #3291为外部工具构建节点树提供了更清晰的接口。结语pytest 3.5.0 虽然是一个小版本却为后续数个版本的能力缓存驱动的测试排序、细粒度输出控制、JUnit 日志集成、统一 verbosity 体系打下了关键基础。对于开发者而言本节最值得立即上手的三个能力是用--show-capturelog收敛失败输出、用--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),仅供参考