AI 一次改几十个文件,测试怎么决定回归范围?

简介: AI编码时代,测试不能只看改了多少文件,而应聚焦业务合约影响。本文提出“代码Diff→业务合约→风险等级→测试集”可追溯链路,通过维护`impact-map.yaml`和CI回归选择器,实现精准、可审计的智能回归,让测试成为交付风险的决策者。

周五傍晚,研发把一个 PR 丢进群里:“AI 辅助改完了优惠计算逻辑,47 个文件,单测全绿,麻烦测一下。”

测试最怕的,往往不是 47 个文件,而是下一句:

“那是不是把回归跑一遍?”

如果每次 AI Coding 改动都全量回归,测试环境、执行时长和人力很快会被压垮;如果只挑几个接口测,又极容易漏掉订单金额、支付金额、优惠券核销这些真正贵的风险。

结论先说:回归范围不该按“改了多少文件”决定,而应该按“哪些业务合约被改变、改变后是否可逆、会波及到哪些下游合约”决定。

AI 可以帮团队写代码、拆文件、改配置、补测试,但不应该由它来拍脑袋决定“测什么”。测试团队要把这件事变成一条可追溯的工程链路:

代码 Diff → 业务合约 → 风险等级 → 测试集 → CI 放行结论

image.png

一、代码 Diff 只能告诉你“改了哪里”,不能告诉你“会赔多少钱”

假设电商团队把优惠计算从订单服务里拆出来,交给 AI Coding 工具协助重构。

这次变更包含:

  • services/promotion/ 下的满减、折扣券、会员券计算;
  • 订单结算页的金额展示;
  • 支付前创建交易单的金额校验;
  • 一份优惠规则配置;
  • 若干测试桩和接口字段。

如果只按文件路径理解风险,测试很容易得到一个错误结论:

改动文件 表面判断 真正需要验证的业务合约
promotion/calculator.py 优惠模块单测 优惠金额不能超过商品金额
order/checkout.py 下单接口回归 应付金额、优惠明细、库存锁定
payment/create_trade.py 支付接口回归 支付金额必须等于订单应付金额
promotion_rules.yaml 配置变更 新老优惠规则兼容、灰度用户一致性

这里的“业务合约”,不是接口文档上的字段说明,而是系统不能被破坏的承诺。

例如:

  • 优惠金额不能超过商品金额;
  • 订单应付金额不能为负;
  • 订单金额和支付交易单金额必须一致;
  • 同一张优惠券不能被重复核销;
  • 降级或回滚后,已创建订单不能重新计算出另一个金额。

AI 生成的代码最容易让人误判的一点是:它可能把一个业务规则拆散到多个文件里,也可能通过重构、抽象、重命名,让 Diff 看起来“只是局部调整”。

但用户不会因为你的代码结构更优雅,就原谅一次多扣款。

二、先维护“业务合约地图”,再让 CI 选择测试

下面是一份可放进仓库维护的 impact-map.yaml。它不依赖某个大模型,也不需要先上复杂平台;核心是让研发、测试、产品对“改动会影响什么”形成共同语言。

policy:
  expand_threshold: 12

contracts:
  coupon_price:
    paths:
      - "services/promotion/**"
      - "schemas/promotion*.py"
    criticality: 5        # 收入和用户感知,最高级
    irreversible: false
    downstream:
      - order_total
      - payment_amount

  order_total:
    paths:
      - "services/order/checkout.py"
      - "services/order/pricing/**"
    criticality: 5
    irreversible: false
    downstream:
      - payment_amount
      - coupon_redeem

  payment_amount:
    paths:
      - "services/payment/**"
    criticality: 5
    irreversible: true    # 已发起支付后的补救成本高
    downstream: []

  coupon_redeem:
    paths:
      - "services/promotion/redeem/**"
    criticality: 4
    irreversible: true
    downstream: []

tests:
  - id: coupon_unit
    command: "pytest -q tests/unit/test_coupon_calculator.py"
    covers: [coupon_price]

  - id: checkout_contract
    command: "pytest -q tests/contract/test_checkout_amount.py"
    covers: [coupon_price, order_total]

  - id: payment_e2e
    command: "pytest -q tests/e2e/test_pay_after_coupon.py"
    covers: [order_total, payment_amount, coupon_redeem]

  - id: rollback_regression
    command: "pytest -q tests/regression/test_coupon_rollback.py"
    covers: [coupon_price, order_total, payment_amount]

这份地图里有两个关键设计。

第一,路径不是测试范围,路径只是合约识别信号
services/promotion/** 被改动,不代表只跑优惠单测;它意味着 coupon_price 这个合约被触发,需要继续判断它会不会影响订单和支付。

第二,不可逆比文件复杂度更重要
支付、扣券、库存锁定这类动作,一旦出现错误,往往不是“重新发布修一下”就能解决。它们应该天然得到更高的回归优先级。

image.png

三、一段真正能放进 CI 的回归选择器

下面的脚本做三件事:

  1. 获取当前 PR 的变更文件;
  2. 从业务合约地图中找出直接受影响的合约;
  3. 当风险超过阈值时,沿下游链路扩大测试范围。

更重要的是:找不到映射时,脚本默认全量回归,而不是默认少跑。

# tools/select_regression.py
import fnmatch
import json
import subprocess
import sys
from collections import deque

import yaml


def changed_files(base_sha: str, head_sha: str) -> list[str]:
    result = subprocess.run(
        ["git", "diff", "--name-only", base_sha, head_sha],
        check=True,
        capture_output=True,
        text=True,
    )
    return [line.strip() for line in result.stdout.splitlines() if line.strip()]


def matches_any(path: str, patterns: list[str]) -> bool:
    return any(fnmatch.fnmatch(path, pattern) for pattern in patterns)


def risk_score(contract: dict) -> int:
    # 业务重要性 × 2:确保金额、支付等核心链路天然优先
    score = contract["criticality"] * 2

    # 发生后无法简单回滚的动作,额外加权
    if contract.get("irreversible"):
        score += 3

    # 下游越多,影响面越大;最多加 3,避免依赖图过度放大
    score += min(len(contract.get("downstream", [])), 3)
    return score


def downstream_closure(seed: set[str], contracts: dict) -> set[str]:
    impacted = set(seed)
    queue = deque(seed)

    while queue:
        current = queue.popleft()
        for target in contracts[current].get("downstream", []):
            if target not in impacted:
                impacted.add(target)
                queue.append(target)

    return impacted


def select_tests(base_sha: str, head_sha: str, map_file="impact-map.yaml"):
    with open(map_file, "r", encoding="utf-8") as f:
        catalog = yaml.safe_load(f)

    contracts = catalog["contracts"]
    files = changed_files(base_sha, head_sha)

    direct = {
   
        contract_id
        for contract_id, contract in contracts.items()
        if any(matches_any(path, contract["paths"]) for path in files)
    }

    # 没有识别出的业务合约,不能假装“没有风险”
    if not direct:
        return {
   
            "mode": "full",
            "reason": "存在未映射文件,按 fail-closed 策略执行全量回归",
            "changed_files": files,
            "tests": [item["command"] for item in catalog["tests"]],
        }

    expanded = set(direct)
    threshold = catalog["policy"]["expand_threshold"]

    for contract_id in direct:
        if risk_score(contracts[contract_id]) >= threshold:
            expanded |= downstream_closure({
   contract_id}, contracts)

    selected = [
        item["command"]
        for item in catalog["tests"]
        if set(item["covers"]) & expanded
    ]

    covered_contracts = {
   
        contract_id
        for item in catalog["tests"]
        for contract_id in item["covers"]
        if item["command"] in selected
    }

    missing = expanded - covered_contracts
    if missing:
        return {
   
            "mode": "full",
            "reason": f"关键合约缺少测试覆盖:{sorted(missing)}",
            "changed_files": files,
            "tests": [item["command"] for item in catalog["tests"]],
        }

    return {
   
        "mode": "targeted",
        "changed_files": files,
        "direct_contracts": sorted(direct),
        "expanded_contracts": sorted(expanded),
        "tests": selected,
    }


if __name__ == "__main__":
    result = select_tests(sys.argv[1], sys.argv[2])
    print(json.dumps(result, ensure_ascii=False, indent=2))

如果 AI 改动了 services/promotion/calculator.pycoupon_price 的风险分为:

业务重要性 5 × 2 + 下游合约 2 = 12

达到阈值后,脚本不只跑优惠单测,还会扩展到:

coupon_price
→ order_total
→ payment_amount
→ coupon_redeem

对应的回归集合是:

tests/unit/test_coupon_calculator.py
tests/contract/test_checkout_amount.py
tests/e2e/test_pay_after_coupon.py
tests/regression/test_coupon_rollback.py

这才是“有依据的扩大回归”,而不是测试同学凭经验说一句:“这次还是全跑吧。”

四、测试用例要守住的,是金额链路的两个不变量

回归选择器只是决定跑哪些测试;真正兜底的,仍然是测试代码中的业务断言。

以“优惠后支付”为例,至少要固定两个不变量:

def test_coupon_price_is_consistent_with_payment(checkout_client, payment_client):
    checkout = checkout_client.create_order(
        items=[{
   "sku": "keyboard", "price": 69900, "count": 1}],
        coupon_code="VIP-100",
    )

    # 不变量 1:应付金额不能为负,优惠不能超过商品金额
    assert checkout["payable_amount"] >= 0
    assert checkout["discount_amount"] <= checkout["goods_amount"]

    payment = payment_client.create_trade(order_id=checkout["order_id"])

    # 不变量 2:支付交易金额必须等于订单最终应付金额
    assert payment["amount"] == checkout["payable_amount"]

这段测试看似简单,但它比“接口返回 200”“优惠字段存在”更接近真实损失。

因为 AI 改代码时,最常见的并不是把接口直接写崩,而是:

  • 计算顺序换了,会员折扣和满减叠加错误;
  • 精度处理变了,分转元后出现一分钱误差;
  • 订单金额更新了,支付交易单仍读取旧字段;
  • 补偿逻辑没同步,回滚后出现重复核券。

image.png

五、别把“精准回归”做成少跑测试的借口

这套方法最容易被误用成一句话:

“脚本只选了 8 条,所以其他测试都不用跑。”

不对。它解决的是 PR 阶段的反馈速度,不是取消全量质量保障。

建议把回归拆成三层:

阶段 目标 执行策略
PR 快速门禁 尽快拦截直接风险 按业务合约自动选测
合并后回归 发现跨模块耦合问题 核心链路全量回归
夜间/发布前 发现低频、慢性问题 全量 + 数据一致性 + 回滚验证

还有一条必须写进团队规则:

新增业务路径、改动跨服务链路、出现未映射文件时,默认扩大,不默认缩小。

GitHub 的 Copilot cloud agent 已经可以在分支中研究仓库、修改代码、执行自动化测试,并在准备后创建 PR。代码产出变快后,测试最需要补上的不是“再加一个 AI 工具”,而是让每一个改动都留下可审计的业务影响证据。GitHub 官方文档

image.png

写在最后

AI Coding 让“改代码”越来越便宜,但它没有让“判断改动影响”变简单。

真正成熟的测试体系,不是跑得最快,也不是跑得最多,而是每次面对一个 PR 时,都能回答清楚:

  • 它改变了哪条业务承诺?
  • 如果错了,用户和公司会付出什么代价?
  • 我们为什么相信这批测试足以支持放行?

当这三个问题能被代码、测试资产和 CI 记录共同回答时,测试才真正从“回归执行者”变成了交付风险的决策者。

相关文章
|
20天前
|
人工智能 缓存 前端开发
DeepSeek Harness 首发实测 + 入门教程,夯爆了!梁神我错了
DeepSeek Harness + DeepSeek V4 Pro 项目实战保姆级教程!手把手带你从零安装开源 AI 编程工具,开发架构图、知识讲解网站、3D 网页游戏、全栈 AI 应用 4 个项目,覆盖运行模式选择、插件安装与开发,看看能不能对标 Claude。
13231 90
DeepSeek Harness 首发实测 + 入门教程,夯爆了!梁神我错了
|
8天前
|
人工智能 自然语言处理 安全
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
本文聚焦阿里云2026年推出的三款自研AI办公产品,清晰拆解千问办公、Qoder Teams、Qoder CN的差异化定位与能力边界:千问办公主打职场全场景提效,支持自然语言指令一键完成PPT生成、数据分析等高频办公任务;Qoder Teams面向程序员团队,深度整合AI代码生成、团队协同与企业知识库能力;Qoder CN则专为金融、政务等强合规场景打造,实现数据不出境与VPC私有化部署。文章同步给出分场景选型指南与最新活动定价,帮助不同类型的企业按需组合产品,实现业务岗、研发岗与强合规场景的AI能力全覆盖。
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
|
3天前
|
缓存 人工智能 API
阿里云Qwen3.8‑Flash完整能力解析:模型特性、API调用实操与计费规则深度拆解
在AI应用快速落地的当下,开发者与企业选型大模型API,不再只单纯关注评测榜单分数,推理速度、上下文长度、多模态能力、工具调用稳定性以及实际调用成本,共同决定项目能否平稳上线。Qwen3.8‑Flash作为新一代多模态混合专家模型,主打高性能推理与低成本开销,面向编程开发、智能Agent工作流、超长文档解析、图文混合理解等高频场景,提供托管API服务,权重同时开放可供本地部署,兼容主流接口协议,能够无缝接入各类开发工具链。很多开发者在接入过程中,容易混淆普通按量Token计费、缓存计费、各类订阅计划之间的差异,造成实际账单超出预估。本文从模型底层架构、核心功能能力、适用场景、API调用实操、完
801 0
|
13天前
|
Web App开发 人工智能 API
16 个超火的 DeepSeek Harness 插件,大肥鱼已经落后 N 个版本了。。。
DeepSeek Harness 精选插件推荐合集,从图片识别、浏览器操控、多 Agent 协作到手机远程控制,一口气带你看完 DSH 社区热门的十几个插件,覆盖技能扩展、UI 界面增强、整活玩法三大类,让你的鲸鱼变得更强。
1792 4
|
14天前
|
人工智能 Java BI
【AI】DeepSeek Harness 安装、运行、管理插件
本文介绍了如何运行DeepSeek开源的Agent框架DeepSeek Harness(dsh)。主要内容包括:使用nvm安装适配的Node版本;通过代理加速克隆GitHub源码;使用pnpm安装依赖并启动项目;配置DeepSeek API Token;安装扩展功能的插件。该框架自带Web界面,支持模型适配、文件编辑等插件化功能
1969 1
|
人工智能 JavaScript 开发工具
DeepSeek Harness 本地安装与使用指南
DeepSeek Harness(DSH)是DeepSeek AI开源的Agent运行框架,支持本地文件操作、命令执行与工具调用。基于Cordis插件架构,具备高扩展性与强可控性,适合开发者搭建可控Agent环境或开展模型基准测试。当前为开发者预览版,需Node.js环境,推荐先用`npx @deepseek-ai/dsh web`快速体验。
5230 0
|
9天前
|
人工智能 Linux iOS开发
Ollama使用教程:Ollama官网下载、Ollama本地部署大模型(2026最新)
Ollama 是一款免费开源的本地大模型运行工具,支持在 Windows/macOS/Linux 上离线运行 Qwen、DeepSeek、Llama 等主流开源模型,数据不出本机、隐私安全。提供 OpenAI 兼容 API,命令行一键拉取/运行/管理模型,无需联网,无调用限制,是开发者与 AI 爱好者部署本地 AI 助手的理想选择。(239 字)
|
16天前
|
人工智能 JavaScript 测试技术
保姆级教程:DeepSeek Harness从安装到跑通测试,30分钟上手
DeepSeek Harness是DeepSeek开源的AI Agent运行时,主打“一行命令安装、5分钟跑通”。它让模型真正动手干活——读代码、跑测试、分析失败、生成修复方案。本文手把手教你30分钟从零上手,覆盖安装、配置、实测及避坑指南,助你快速掌握下一代AI编程范式。
|
6天前
|
人工智能 监控 测试技术
Qwen3.8-Flash 来了,100万上下文、Agent、Coding 都加强了
8月26日,通义千问发布Qwen3.8-Flash-Next:125B参数、每Token仅激活6B,原生支持26万Token、可扩展至100万上下文;Coding、Agent与工具调用能力显著增强,面向真实软件工程任务,推动大模型从“回答问题”迈向“完成工作”。