ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

marimo 仓库的 pytest-changed 插件:基于 Ruff 依赖图精准运行受影响测试

marimo 仓库的 pytest-changed 插件:基于 Ruff 依赖图精准运行受影响测试 marimo 仓库的 pytest-changed 插件基于 Ruff 依赖图精准运行受影响测试【免费下载链接】marimoA reactive notebook for Python — run reproducible experiments, query with SQL, execute as a script, deploy as an app, and version with git. Stored as pure Python. All in a modern, AI-native editor.项目地址: https://gitcode.com/GitHub_Trending/ma/marimo本文介绍 marimo 开源仓库中内置的 pytest 插件packages/pytest_changed它通过git diff找出你改动过的 Python 文件再利用 Ruff 的analyze graph能力构建项目依赖图经 BFS 图遍历定位所有受影响文件最终只运行其中真正可能受影响的测试。读完本文你将掌握该插件的完整用法对比分支、预览、包含未改动测试、限定目录、全部命令行参数以及其底层五个阶段的源码级实现原理可直接在 marimo 或任何基于 git pytest 的 Python 项目中落地使用。插件解决的问题全量测试的浪费在一个大型 Python 仓库中每次提交前跑完整测试套件非常耗时。以 marimo 仓库为例其 pyproject.toml 定义了test与test-optional两组依赖测试覆盖了运行时tests/_runtime/、AST 编译tests/_ast/、服务端tests/_server/、插件tests/_plugins/等几十个目录全量回归的成本很高。packages/pytest_changed的定位是按变更智能缩小测试范围它分析你的 git 变更通过依赖图搜索找出所有受变更影响的文件然后只运行可能被影响的测试。它不依赖任何启发式规则如改了哪个模块就测哪个模块而是基于 Ruff 对 Python 导入关系包括字符串形式的导入的静态分析做到既有精确度又避免遗漏下游依赖方。快速上手激活与基本用法该插件以 pytest 内置插件-p方式激活无需安装额外包。在 marimo 仓库根目录执行pytest -p packages.pytest_changed --changed-frommain--changed-frommain指定与main分支对比找出自main以来发生变更的文件并只运行受影响的测试。使用 uv 运行推荐可同时锁定 Python 版本与依赖组uv run --python 3.12 --group test pytest -p packages.pytest_changed --changed-frommain tests/这里--python 3.12指定解释器版本--group test使用 pyproject.toml 中[dependency-groups]定义的test依赖组包含pytest~9.0.2、pytest-picked0.5.1等tests/作为测试根目录参数。对比不同的 git 引用插件默认支持任意 git ref常见场景# 对比 HEAD检测暂存区staged以来的改动 pytest -p packages.pytest_changed --changed-fromHEAD # 对比远程分支 pytest -p packages.pytest_changed --changed-fromorigin/main从源码看--changed-from的值会被原样传给git diff --name-only ref见 packages/pytest_changed/init.py因此只要是 git 能解析的引用分支名、tag、HEAD~3等都可以使用。预览将要运行的测试不想真正执行测试时用 pytest 自带的--collect-only仅收集并打印被选中的用例pytest -p packages.pytest_changed --changed-frommain --collect-only tests/这在确认插件选集是否符合预期、或在 CI 调试时非常有用。包含未改动的测试--include-unchanged会在受影响的测试之外额外运行所有其他测试相当于受影响测试优先、全量兜底pytest -p packages.pytest_changed --changed-frommain --include-unchanged tests/值得注意的实现细节是一旦设置了该选项插件会直接跳过变更检测与依赖图构建这两步是整个流程中最昂贵的部分因为最终反正要跑全部测试。对应源码在pytest_configure中if config.getoption(include_unchanged): print_(--include-unchanged set - running all tests) config.option.changed_test_files None return见 packages/pytest_changed/init.py。限定到指定测试目录插件尊重 pytest 常规的路径过滤语义你可以在命令尾部传入目录将选集进一步约束到该目录内该目录会同时成为测试文件过滤的基准pytest -p packages.pytest_changed --changed-frommain tests/_runtime/底层通过get_test_base从 pytest 调用参数中解析第一个存在的目录作为test_base只有位于该目录下的受影响文件才会被视为测试文件见 packages/pytest_changed/init.py。配置选项一览插件在pytest_addoption中注册了两个选项归属ruff-graph参数组见 packages/pytest_changed/init.py选项类型默认值说明--changed-fromrefstoreNone要对比的 git 引用如HEAD、main、origin/main。该选项是激活插件的必要条件未提供时插件不执行任何分析pytest 按常规模式运行--include-unchangedstoreFalse布尔开关值为true/1/yes不区分大小写时开启。开启后除受影响测试外还运行全部未受影响的测试两个选项的解析行为也可以从源码确认--changed-from未设置时pytest_configure直接returnL238-L241--include-unchanged使用typelambda x: x.lower() in (true, 1, yes)解析字符串布尔值L44。工作原理五个阶段的源码级剖析插件的执行逻辑完整实现在 packages/pytest_changed/init.py约 376 行无第三方依赖仅用标准库subprocess/json/collections.deque与 pytest API。其流程分为五步阶段 1用 git diff 找出变更文件get_changed_files执行git diff --name-only ref只保留存在且后缀为.py的文件result subprocess.run( [git, diff, --name-only, ref], cwdrepo_root, capture_outputTrue, textTrue, checkTrue, )见 packages/pytest_changed/init.py。该函数同时返回一个critical_changed标志——如果变更文件中包含pyproject.toml这类影响所有测试的配置文件插件会放弃缩小范围直接运行全部测试L272-L279。当前仓库中critical_files只包含pyproject.tomlL62因为它集中定义了依赖、pytest 配置与打包元数据改动它意味着任何测试的运行时环境都可能变化。阶段 2用 ruff analyze graph 构建依赖图get_dependency_graph调用uvx运行固定版本的 Ruff[ uvx, ruff0.15.18, analyze, graph, --detect-string-imports, --direction, direction, ., ]见 packages/pytest_changed/init.py。几个关键决策固定ruff0.15.18源码注释明确说明因为 Ruff 的analyze graph尚属实验性功能固定版本可避免输出格式或行为变化带来的解析失败L118-L120--detect-string-imports让 Ruff 额外识别字符串形式动态导入避免遗漏这类隐式依赖--direction dependents图的方向是谁依赖了我这正是寻找下游受影响文件的正确方向输出为 JSON解析后统一转换为绝对路径再参与后续遍历L135-L145。阶段 3BFS 图遍历找出所有受影响文件find_affected_files以变更文件为起点做广度优先搜索BFS沿dependents边扩散用visited集合处理环与去重affected: set[Path] set(changed_files) visited: set[str] set() queue: deque[Path] deque(changed_files) while queue: current queue.popleft() ... dependents dependency_graph.get(current_str, []) for dependent in dependents: ...见 packages/pytest_changed/init.py。由于 Python 模块间依赖可能形成环A 导入 B、B 也导入 Avisited集合保证了遍历必然终止结果集合包含变更文件本身及其全部间接下游例如改动marimo/_runtime/cell.py后直接导入它的模块、再导入这些模块的模块都会被逐层波及。阶段 4过滤出测试文件find_test_files按三条规则过滤受影响文件文件存在、文件名以test_开头marimo 的测试命名约定、且位于test_base目录之下packages/pytest_changed/init.py。test_base默认取 pytest 调用参数中第一个存在的目录否则回退到仓库根目录L192-L207这也是上文中限定目录用法能生效的原因。阶段 5运行选中的测试插件通过两个 pytest hook 完成最后的选集注入pytest_configure执行上述全部分析。找到受影响测试后将文件列表写入config.option.changed_test_files并把config.args直接替换为受影响测试文件路径L316-L322。分析过程中的关键决策都会打印到终端变更文件清单、受影响文件数、选中的测试文件方便人工核验pytest_collection_modifyitems收集完成后按changed_test_files对用例做二次筛选。三种分支语义清晰L338-L376changed_test_files is None全量运行对应--include-unchanged或配置文件变更的场景空集合所有用例被标记为skip跳过理由为Not affected by changes from ref非空集合只保留属于受影响文件的用例其余通过pytest_deselected钩子排除并打印运行 N 个用例、跳过 M 个用例的摘要。此外仓库根目录通过git rev-parse --show-toplevel获取L246-L254保证在子目录中执行命令也能正确定位仓库。仓库内的工程化实践跨版本矩阵脚本marimo 没有止步于插件的裸调用而是将其封装进了 scripts/pytest-changed.sh该脚本在Python 3.10 / 3.11 / 3.12 / 3.13四个版本、test-optional依赖组上循环执行插件测试并对每个版本汇总通过/失败结果。其核心调用为uv run --python $PY_VERSION --group $GROUP pytest tests/ \ -v \ -k not test_cli \ --durations10 \ -p packages.pytest_changed \ --changed-from$CHANGED_FROM \ --include-unchangedfalse \ --pickedfirst \ $见 scripts/pytest-changed.sh。其中--python/--group/--from分别控制版本、依赖组与对比引用默认main--pickedfirst来自test依赖组中的pytest-picked插件与 pytest-changed 配合使用--include-unchangedfalse显式关闭兜底全量确保矩阵测试只跑受影响用例结尾的$允许透传额外 pytest 参数如-k test_foo -x。在 CI 场景下插件源码还对对比引用不存在给出了明确提示如果 checkout 时未拉取完整历史例如 GitHub Actions 的actions/checkout默认浅克隆git diff会失败此时需要在检出步骤配置fetch-depth: 0见 packages/pytest_changed/init.py 中的错误信息。使用边界与注意事项结合源码可以总结出以下适用前提与限制帮助你在自己的项目中正确使用必须位于 git 仓库内插件依赖git rev-parse --show-toplevel非 git 目录会直接以错误退出L253-L254只追踪.py文件git diff结果中非 Python 文件如前端.ts、配置.json不会触发测试选择除非命中pyproject.toml这类关键文件L85测试文件按test_*.py命名约定识别如果你的测试命名不符合该约定需要自行调整find_test_files中的过滤逻辑L226-L230依赖图依赖 Ruff 的静态分析ruff analyze graph目前仍标记为实验性插件通过固定ruff0.15.18版本规避不稳定性导入关系极为动态如运行时动态拼接模块名的场景可能无法被完整捕捉--include-unchanged会短路整个分析它不是受影响测试优先执行而是全部执行只是用选项语义表达了不遗漏任何测试的兜底诉求L259-L262。总结packages/pytest_changed是 marimo 仓库中一个小而完整的工程工具核心逻辑全部集中在 packages/pytest_changed/init.py 单文件内通过git diffruff analyze graph BFS 遍历 pytest hook 的组合实现了依赖感知的精准测试选集并配合 scripts/pytest-changed.sh 在多 Python 版本矩阵中落地。无论你是 marimo 的贡献者还是想在自有 Python 项目中复刻改多少、测多少的开发体验都可以直接以该插件为模板——其无第三方运行时依赖、仅依赖 git 与 uvx 的设计让移植成本几乎为零。【免费下载链接】marimoA reactive notebook for Python — run reproducible experiments, query with SQL, execute as a script, deploy as an app, and version with git. Stored as pure Python. All in a modern, AI-native editor.项目地址: https://gitcode.com/GitHub_Trending/ma/marimo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表