把需求变更拆成测试用例后,AI 输出稳定了很多

需求变更最容易漏测。尤其是后端接口、小程序活动页、会员权益、订单状态流转这类业务,产品文档里一句"新增一种优惠券适用规则",落到测试阶段可能就变成十几种边界条件:新老用户、过期时间、叠加规则、退款后状态、库存占用、灰度开关......靠经验当然能补一部分,但很难保证不漏。

我最近更愿意把 ChatGPT、Claude、Gemini、DeepSeek、Grok 这类模型放在"测试用例生成前的拆解环节",而不是直接让它们给一份最终测试用例。原因很简单:AI 直接生成用例时,看起来很完整,但经常夹杂假设;先让它做规则归纳、风险点枚举、边界条件补全,再由测试或开发确认,落地会稳很多。

下面以一个比较常见的"优惠券规则变更"为例,记录一下我会怎么把 AI 放进测试用例设计流程。

场景:优惠券规则改了,但文档只有几行

假设产品给了这样一段需求:

text

复制代码
订单结算页支持新优惠券规则:
1. 新增"满 100 减 20"平台券;2. 平台券仅限普通商品使用,虚拟商品不可用;3. 平台券可与会员折扣同时使用;4. 平台券不可与店铺券叠加;5. 订单发生部分退款时,按商品实付金额比例退还优惠金额;6. 活动时间为 2026-01-01 00:00:00 至 2026-01-31 23:59:59;7. 老版本客户端不展示平台券入口,但后端仍需兼容已领取用户的结算请求。

这段需求不长,但测试点不少。直接让 AI "生成测试用例",通常会有两个问题:

  • 它会默认一些需求没写清楚的规则,比如"满 100"是否包含运费;
  • 它会混合输出"已确定规则"和"建议确认点",导致评审时很难使用。

所以第一步不要生成用例,而是先做拆解。

第一步:先让 AI 做规则归纳,不要写用例

我会先用这个 Prompt:

text

复制代码
你是电商后端测试分析助手。请根据下面的需求说明做规则归纳,不要生成测试用例。
输出要求:1. 列出已经明确的业务规则;2. 列出不明确或可能有歧义的点;3. 列出需要产品或开发确认的问题;4. 列出可能影响测试范围的系统模块;5. 不要编造需求中没有提到的规则。
需求如下:【粘贴需求】

比较有价值的输出应该是这种结构:

text

复制代码
已明确规则:- 平台券类型:满 100 减 20;- 适用商品:普通商品;- 不适用商品:虚拟商品;- 可叠加:会员折扣;- 不可叠加:店铺券;- 活动有效期:2026-01-01 00:00:00 ~ 2026-01-31 23:59:59;- 老版本客户端不展示入口,但后端需要兼容结算请求。
不明确点:- 满 100 是否按商品原价、会员折扣后金额、还是订单应付金额判断;- 100 元门槛是否包含运费;- 普通商品和虚拟商品混合下单时,平台券如何分摊;- 部分退款时,优惠金额比例是否四舍五入;- 老版本客户端如何传 platformCouponId;- 店铺券与平台券同时传入时,后端返回错误还是自动择优。

这一步的意义是把"可能漏测的地方"提前暴露出来。很多测试遗漏不是因为不会写用例,而是需求阶段没有把规则问清楚。

第二步:把确认后的规则变成测试维度表

等产品和开发确认规则后,再让 AI 生成测试维度,而不是直接生成几十条用例。

假设确认结果如下:

text

复制代码
补充确认:- 满 100 按会员折扣后的商品实付金额判断,不包含运费;- 普通商品和虚拟商品混合下单时,仅普通商品金额参与门槛计算;- 平台券和店铺券同时传入时,后端返回 400;- 部分退款优惠分摊保留 2 位小数,采用四舍五入;- 老版本客户端如传入 platformCouponId,后端正常计算。

Prompt 可以这样写:

text

复制代码
请根据"原始需求 + 补充确认"生成测试维度表。
要求:1. 不直接生成测试用例;2. 按功能规则、边界条件、异常输入、兼容性、退款场景、时间场景分类;3. 每个维度说明测试目的;4. 标记哪些维度适合自动化测试;5. 输出 Markdown 表格。

输出可以整理成这样:

分类 测试维度 测试目的 是否适合自动化
门槛金额 折扣后普通商品金额 = 99.99 验证未达到满减门槛
门槛金额 折扣后普通商品金额 = 100.00 验证刚好达到门槛
商品类型 仅虚拟商品 验证不可使用平台券
商品类型 普通商品 + 虚拟商品 验证仅普通商品参与门槛
叠加规则 平台券 + 会员折扣 验证允许叠加
叠加规则 平台券 + 店铺券 验证返回 400
退款 部分退款普通商品 验证优惠金额按比例退还
时间 活动开始前 1 秒 验证不可用
时间 活动结束后 1 秒 验证不可用
兼容性 老版本客户端传平台券 ID 验证后端兼容

到这一步,测试范围已经比较清楚了。后面再生成具体用例,会比一开始就让 AI "写测试用例"靠谱得多。

第三步:生成可执行的测试用例草稿

接下来才进入用例生成。Prompt 要尽量约束输出格式,方便后续导入测试管理平台或转成自动化脚本。

text

复制代码
请根据下面的测试维度生成接口测试用例草稿。
接口背景:- POST /api/order/checkout- 请求参数包含 userId、items、platformCouponId、shopCouponId、clientVersion- 返回 totalAmount、discountAmount、payAmount、errorCode
要求:1. 每条用例包含:用例标题、前置条件、请求输入、预期结果、验证点;2. 只使用已确认规则;3. 金额保留两位小数;4. 异常用例要写明预期 errorCode;5. 输出 JSON 数组,便于后续处理。

示例输出可以长这样:

json

复制代码
[  {    "title": "普通商品折扣后金额刚好100元时可使用平台券",    "precondition": "用户已领取满100减20平台券,活动处于有效期内",    "request": {      "userId": "user_001",      "items": [        {          "skuId": "normal_sku_001",          "type": "NORMAL",          "originAmount": "125.00",          "memberDiscountAmount": "25.00"        }      ],      "platformCouponId": "platform_coupon_001",      "shopCouponId": null,      "clientVersion": "3.8.0"    },    "expected": {      "discountAmount": "20.00",      "payAmount": "80.00",      "errorCode": null    },    "checkpoints": [      "会员折扣后普通商品金额为100.00",      "平台券满减生效",      "最终应付金额为80.00"    ]  },  {    "title": "平台券和店铺券同时传入时返回错误",    "precondition": "用户同时拥有平台券和店铺券",    "request": {      "userId": "user_001",      "items": [        {          "skuId": "normal_sku_001",          "type": "NORMAL",          "originAmount": "120.00",          "memberDiscountAmount": "0.00"        }      ],      "platformCouponId": "platform_coupon_001",      "shopCouponId": "shop_coupon_001",      "clientVersion": "3.8.0"    },    "expected": {      "errorCode": "COUPON_CANNOT_STACK"    },    "checkpoints": [      "后端不自动择优",      "返回明确错误码",      "不生成结算结果"    ]  }]

这类 JSON 不能直接算"最终测试用例",但已经可以作为测试评审材料,也可以继续转成自动化测试脚本。

第四步:把用例转成自动化测试骨架

如果项目是 Java + JUnit,可以让 AI 根据 JSON 草稿生成测试骨架。这里仍然要注意:让它生成"骨架",不要让它替你虚构项目里的 Fixture、Repository 或 Mock 细节。

Prompt:

text

复制代码
请根据下面的接口测试用例 JSON,生成 JUnit 5 测试骨架。
限制:1. 不要编造项目中不存在的工具类;2. 用伪方法表示数据准备,例如 createUser、createCoupon、createSku;3. 重点体现测试结构、断言和边界;4. 金额使用 BigDecimal;5. 输出 Java 代码。

示例代码:

java

复制代码
class OrderCheckoutCouponTest {
    @Test    void shouldApplyPlatformCouponWhenDiscountedNormalAmountEquals100() {        // given        User user = createUser("user_001");        Coupon platformCoupon = createPlatformCoupon("platform_coupon_001", "100.00", "20.00");
        Sku normalSku = createSku("normal_sku_001", ProductType.NORMAL, new BigDecimal("125.00"));        createMemberDiscount(user, normalSku, new BigDecimal("25.00"));
        CheckoutRequest request = new CheckoutRequest();        request.setUserId(user.getId());        request.setItems(List.of(new CheckoutItem(normalSku.getId(), 1)));        request.setPlatformCouponId(platformCoupon.getId());        request.setShopCouponId(null);        request.setClientVersion("3.8.0");
        // when        CheckoutResponse response = orderCheckoutService.checkout(request);
        // then        assertNull(response.getErrorCode());        assertEquals(new BigDecimal("20.00"), response.getDiscountAmount());        assertEquals(new BigDecimal("80.00"), response.getPayAmount());    }
    @Test    void shouldRejectWhenPlatformCouponAndShopCouponUsedTogether() {        // given        User user = createUser("user_001");        Coupon platformCoupon = createPlatformCoupon("platform_coupon_001", "100.00", "20.00");        Coupon shopCoupon = createShopCoupon("shop_coupon_001");
        Sku normalSku = createSku("normal_sku_001", ProductType.NORMAL, new BigDecimal("120.00"));
        CheckoutRequest request = new CheckoutRequest();        request.setUserId(user.getId());        request.setItems(List.of(new CheckoutItem(normalSku.getId(), 1)));        request.setPlatformCouponId(platformCoupon.getId());        request.setShopCouponId(shopCoupon.getId());        request.setClientVersion("3.8.0");
        // when        CheckoutResponse response = orderCheckoutService.checkout(request);
        // then        assertEquals("COUPON_CANNOT_STACK", response.getErrorCode());        assertNull(response.getPayAmount());    }
    private User createUser(String userId) {        // TODO: replace with project fixture        return null;    }
    private Coupon createPlatformCoupon(String couponId, String threshold, String discount) {        // TODO: replace with project fixture        return null;    }
    private Coupon createShopCoupon(String couponId) {        // TODO: replace with project fixture        return null;    }
    private Sku createSku(String skuId, ProductType type, BigDecimal price) {        // TODO: replace with project fixture        return null;    }
    private void createMemberDiscount(User user, Sku sku, BigDecimal discountAmount) {        // TODO: replace with project fixture    }}

这段代码的价值不是直接复制运行,而是给测试开发一个清晰的结构:数据准备、请求构造、调用接口、断言结果。真实落地时,要替换成项目里的测试基类、Fixture、Mock Server 或 Testcontainers 环境。

不同模型适合放在哪些环节

这类任务可以不指定某一个模型。我的经验是,按阶段使用会更自然:

  • Claude:适合读较长需求、PRD、评审纪要,归纳规则比较细;
  • ChatGPT:适合把确认后的规则转成用例、脚本和代码骨架;
  • Gemini:适合表格化整理、多份资料合并、生成结构化清单;
  • DeepSeek:适合中文技术语境下解释业务逻辑、补充边界条件;
  • Grok:适合提出一些非常规风险,比如历史客户端、异常状态、灰度开关;
  • Seedance 2.0:更适合把复杂流程做成短视频分镜或产品演示,不适合直接判断测试结论;
  • ChatGPT Image 2.0:适合生成流程图、测试覆盖图、技术文章配图,但图片涉及商用时要检查版权、商标、隐私和平台规范。

多模型对比不是为了找"标准答案"。同一份需求交给不同模型看,重点是观察它们分别漏掉了什么。测试设计最怕盲区,多一个视角往往比多十条重复用例更有用。

AI 生成测试用例后,怎么验证

AI 给出的用例一定要过几道检查。

1. 对照需求确认记录

所有用例都要能追溯到需求原文或补充确认。

如果模型写出"平台券可与运费券叠加",但需求里没说,那就必须删掉或标记为待确认。

2. 对照接口定义

字段名、错误码、金额格式、状态枚举,都要回到接口文档、OpenAPI、后端 DTO 中确认。AI 很容易写出看起来合理但项目里不存在的字段。

可以用一个简单脚本检查用例 JSON 中的字段是否超出接口定义:

python

复制代码
allowed_request_fields = {    "userId",    "items",    "platformCouponId",    "shopCouponId",    "clientVersion"}
test_cases = [    {        "title": "平台券和店铺券同时传入时返回错误",        "request": {            "userId": "user_001",            "items": [],            "platformCouponId": "platform_coupon_001",            "shopCouponId": "shop_coupon_001",            "clientVersion": "3.8.0",            "couponType": "PLATFORM"        }    }]
for case in test_cases:    extra_fields = set(case["request"].keys()) - allowed_request_fields    if extra_fields:        print(f"[WARN] {case['title']} contains unknown fields: {extra_fields}")

输出:

text

复制代码
[WARN] 平台券和店铺券同时传入时返回错误 contains unknown fields: {'couponType'}

这种检查很简单,但能拦住不少"AI 自己补字段"的问题。

3. 对照代码实现

如果后端实际逻辑是先计算平台券再计算会员折扣,而需求确认是"会员折扣后金额判断门槛",那就不是测试用例问题,而是实现和需求不一致。测试设计阶段发现这类差异,成本最低。

4. 对照历史兼容

老版本客户端、新老接口、灰度开关、历史订单状态,AI 默认不一定会想到。涉及兼容场景时,最好把版本矩阵单独列出来。

例如:

clientVersion 是否展示平台券入口 是否允许传 platformCouponId 预期
3.6.0 后端兼容计算
3.8.0 正常计算
3.8.0 不使用平台券

多模型工具怎么选,不看模型数量看流程适配

如果团队想把 AI 放进测试或研发流程,我会更关注这些点:

  1. 是否方便把同一份需求交给不同模型对比;
  2. 是否支持较长上下文,能放下 PRD、接口文档、错误码和评审纪要;
  3. 是否方便导出 Markdown、JSON、CSV;
  4. 是否支持会话留存,方便追溯某次用例设计的依据;
  5. 是否能区分草稿、确认项、待确认项;
  6. 是否方便做输入脱敏;
  7. 是否支持文本、图像、视频等多模态任务,便于后续做流程图、培训素材或演示材料;
  8. 是否适合团队协作,而不是只服务个人聊天。

模型能力当然重要,但测试工作更看重可追溯、可复核、可执行。不能导出、不能沉淀、无法复盘的输出,很难进入团队流程。

风险边界:这些内容别直接丢给 AI

测试和研发资料里经常包含敏感信息,使用 AI 前最好做脱敏。

不要直接提交:

  • 真实用户手机号、地址、身份证、邮箱;
  • 生产订单号、支付流水号、退款单号;
  • 内部接口域名、数据库地址、访问 Token;
  • 完整生产日志;
  • 未公开活动策略或商业规则;
  • 客户名称、合同编号、渠道分成信息;
  • 安全漏洞细节。

可以把真实值替换成占位符:

text

复制代码
user_001order_001coupon_001example.internaltoken_xxx

AI 需要理解的是规则结构,不需要知道真实业务数据。

如果后续用图像或视频模型生成测试流程图、培训视频、产品演示,也要检查截图、Logo、商标、人物肖像、字体授权和平台发布规范。多模态结果看起来更直观,但泄露信息的风险也更隐蔽。

FAQ:几个常见误区

1. AI 生成的测试用例能直接放进测试管理平台吗?

不建议直接放。至少要经过需求追溯、字段校验、错误码确认和人工 Review。AI 输出更适合作为草稿和覆盖面补充。

2. 单一模型够不够?

普通需求够用。涉及复杂金额、权限、兼容、历史状态时,建议用两个以上模型交叉看一遍,重点不是比较文风,而是找遗漏点。

3. Prompt 怎么写会更稳定?

不要只写"帮我生成测试用例"。更稳的方式是分三步:先归纳规则,再列不确定点,最后生成可执行用例。每一步都限制它不要编造规则。

4. 如何避免 AI 编造接口字段?

让模型只基于接口文档输出,并要求每个字段标注来源。生成后再用脚本或人工对照 OpenAPI、DTO、接口文档检查。

5. AI 会不会替代测试设计?

短期看更像增强工具。它能补充边界条件、整理规则、生成草稿,但判断业务风险、确认优先级、设计回归策略,仍然需要测试和研发共同负责。

总结

把 AI 用在测试用例生成里,关键不是让它一次性给最终答案,而是把任务拆开:需求规则归纳、歧义点确认、测试维度整理、用例草稿生成、自动化骨架生成、结果验证。

对于 CSDN 读者来说,更推荐从一个高频、低风险、可验证的需求变更开始试。Prompt 写清楚边界,输入资料先脱敏,输出结果必须对照需求、接口、代码和测试环境验证。重要场景可以用多模型交叉检查,但最终结论仍然要由团队确认。

AI 可以帮我们少漏一些测试点,也能减少整理用例的重复劳动。但测试质量不会因为用了模型自动变好,真正起作用的还是清晰规则、可执行用例和稳定的验证闭环。