基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
  • 知识卡片碎片时间过考点
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
    • AI 热点每日 AI 资讯与动向
AI 工作台
简历
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
编程提效2026-11-09 01:34

AI 补丁挤满 CI,如何筛选必跑测试

dev.to · AI#AI编程#持续集成#测试
Editor brief · 编辑速览

面对大量模型生成的补丁,文章建议设置始终执行的测试底线,再选择性运行其他测试以控制验证成本。筛选器必须通过历史补丁回放测量真实故障漏检数,不能仅凭节省的运行时间判断效果。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

AI 生成补丁越来越快,CI 却未必跑得过来。原文给出的办法是:根据改动选择测试,保留一组每次必跑的测试,再用历史补丁回放,检查到底漏掉了多少真实失败。能否省时间,要先过这道验证。

这套方案值得读的地方,是把「少跑测试」变成了可以审计的工程决策。不过,作者给的是 Python/pytest 原型,没有公布实际节省的时间或失败召回率。代码里的依赖盲区、冻结冲突和审计边界,也需要一起看。

补丁排队之后,为什么不能只盯着全量测试

作者设定的场景是:人类每一两个小时提交一个 PR,Agent 队列却可能每小时生成几十个候选补丁。如果测试套件耗时 12 分钟,30 个候选就要消耗 6 小时 runner 时间。这是累计计算资源开销,并不等于并行运行后的实际等待时间,但足以解释验证为什么会成为瓶颈。

这里要区分两个职责:门禁(gate)决定补丁能否接受,测试选择器(selector)决定为这个判断购买哪些证据。门禁规则再严格,也只能处理已经拿到的测试结果。

选错测试最危险的结果,是该出现的失败根本没有进入报告。

原文提到 MonkeyCode 的免费模型访问和免费服务器选项,也披露文章属于其产品推广内容。作者同时承认,这些只是运营方提供的可用性声明,不保证容量。方案是否成立,不能依赖免费资源长期充足;团队仍然要测自己的运行时间,有 SLA 时还要准备备用 runner。

依赖记录能缩小范围,必跑测试负责补洞

第一步不是只分析静态 import,而是在真实测试运行中记录依赖。原因很具体:测试还会通过 conftest.py、fixture、模块级注册表和数据加载器接触业务代码,静态导入关系覆盖不了全部路径。

作者的 conftest.py 钩子为每个测试记录 files、fixtures、mutable_fixtures 和 seconds。输出位置由环境变量 TEST_MANIFEST 指定,默认是 manifest.json;耗时保留三位小数。

但这份清单只是近似记录。它比较测试开始前后 sys.modules 中的仓库文件,已经导入的模块不会再次出现在差集中。会话启动时就加载完依赖的测试,甚至可能看起来没有依赖。因此,「清单里没关联」不能直接推导出「这条测试不受影响」。

针对会修改共享状态的 fixture,作者要求显式标记:

@pytest.mark.mutable_fixture("billing_db")
def test_invoice_roundtrip(billing_db):
    ...

这段标记解决的是调度冲突。要看的是 mutable_fixture("billing_db"):选择器据此找出共享同一可变资源的测试,并输出串行执行建议;它不会自己完成串行调度。

选择阶段再把改动路径映射到测试,合入必跑集合 floor,扣除冻结集合 freeze。核心计算是 (touched | floor) - freeze。这里的必跑集合,就是即使依赖记录没命中,也必须执行的安全底线。

脚本提供 --manifest、--changed、--freeze、--floor、--budget-seconds 和 --out。其中 --changed 必填,其余文件默认分别为 manifest.json、freeze.json、floor.json 和 selection.json,预算默认不设置。

报告中的 estimate_seconds 是上次耗时之和,保留两位小数。它只能辅助排期:补丁可能让测试变慢,并行与串行安排也会影响实际等待时间。超过预算时,脚本返回退出码 2,提示只跑底线集合或拆分补丁,并不会自动完成这些操作。

省下的测试有没有代价,要靠回放回答

冻结测试不能被当成已经覆盖。原文用 tests/test_export.py::test_csv_export_roundtrip 举例:如果它被冻结,修改 export.py 后本该执行却被跳过,就应出现在 skipped_frozen 中,并能找到具名负责人。

另一个字段 gap_floor_frozen 更关键,它记录冻结集合与必跑集合的交集。原文要求交集必须为空,否则应解冻测试或更换底线条目。

这里存在一个实现缺口:代码虽然报告冲突,却没有因此返回失败;计算时仍然会把冻结项从底线集合里扣掉。接入 CI 时,调用方必须把这个字段非空视为错误,否则「必跑」只是配置意图。

最后一步是历史回放:对每个历史补丁运行选择器和完整测试套件,再比较失败测试是否被选中。下面这段审计代码最能体现作者的判断标准:

#!/usr/bin/env python3
"""audit_selection.py — replay patches, measure escaped failures."""
import json, pathlib, sys

def failures(path):
    text = pathlib.Path(path).read_text().splitlines()
    return {l.split(" ", 1)[1].strip() for l in text if l.startswith("FAILED ")}

def main(history_dir):
    rows, escaped_total = [], 0
    for case in sorted(pathlib.Path(history_dir).iterdir()):
        if not (case / "selection.json").exists():
            continue
        selected = set(json.loads((case / "selection.json").read_text())["selected"])
        failed = failures(case / "full_suite.txt")
        escaped = sorted(failed - selected)
        escaped_total += len(escaped)
        rows.append({
            "case": case.name,
            "failed": len(failed),
            "caught": len(failed & selected),
            "escaped": escaped,
            "recall": round(len(failed & selected) / len(failed), 3) if failed else None,
        })
    print(json.dumps(rows, indent=2))
    print(f"escaped failures across {len(rows)} replayed patches: {escaped_total}")
    return 1 if escaped_total else 0

if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1]))

要看的是 escaped = sorted(failed - selected):完整套件发现失败,但选择器没有选中,才算一次漏检。recall 则是被选中的失败数除以全部失败数,保留三位小数;没有失败时记为 None。

这段脚本只读取已有的 selection.json 和 full_suite.txt,不会自动应用历史补丁或执行测试。失败解析也只认以 FAILED 开头的行,因此输入报告必须与它的格式匹配。

作者建议保留漏检案例,把相关测试作为底线候选,并追查会话级导入、冻结依赖或动态 fixture 等原因。回放的价值正在这里:它不仅给出一个分数,还能暴露选择规则反复遗漏的路径。

我的判断

我认同「先量漏检,再谈节约」这个顺序。比起随机抽测,这套方法有明确的选择依据;比起每次全量运行,它有机会降低候选队列的验证成本。但作者没有给对照数据,不能据此认定它已经兼顾了速度和可靠性。

必跑集合只能补已知漏洞,历史回放也只能验证历史里出现过的问题。

还有一层边界:审计把「失败测试被选中」计为捕获,却没有验证它在子集环境里是否仍然失败。如果问题依赖执行顺序或共享状态,选中测试也未必足够。它衡量的是选择召回,不能单独证明整个验证流程可靠。

原文也明确排除了收益不足的场景:少于大约 100 个快速测试时,直接全跑通常更合适;大量会话级导入会让依赖记录系统性缺失;没有回放历史时,选择器仍然只是一项猜测。对普通团队来说,维护清单、底线、冻结负责人和回放样本,本身也有成本。

能拿来做什么

  • 如果团队已经积压大量 AI 候选补丁,先记录完整套件耗时和候选数量,再让选择器旁路生成报告,同时继续全量测试,积累自己的漏检数据。
  • 如果前端或 Node 项目已有测试依赖信息,可以借鉴「按改动选择、合入必跑集合、审计漏检」的流程;原文实现依赖 pytest,不能直接移植后就假定有效。
  • 把 gap_floor_frozen 非空设为流水线错误,为每条 skipped_frozen 关联负责人,并让 runner 实际执行 serialize 中的串行约束。
  • 保留每次漏检的补丁、选择报告和完整测试结果,定期重新回放;超出预算时明确选择拆补丁、调整排期或执行底线集合,避免把预算不足当成验证通过。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
OpenMausBot:在聊天界面调度本地编码代理
下一篇
重构前锁定行为,再用变异验证测试

资讯速览

dev.to · AI · 2026-11-09 01:34

AI 评分 8.0 / 10

相关标签

#AI编程#持续集成#测试

继续阅读

  • 借助 Claude 实测 SQLite 扩展的 C 与 Rust
  • OpenMausBot:在聊天界面调度本地编码代理
  • 重构前锁定行为,再用变异验证测试
  • AI 删掉输入校验后,先查清信任边界
  • 用 Claude 从千万条社区评论筛选视频