只给业务规则,让智能体做黑盒验收
作者让智能体仅依据业务规则生成测试,并对运行中的应用执行黑盒验证,输出违反规则的问题清单。文章介绍从规则输入、用例生成到执行和报告的流程,减少测试迎合现有实现的偏差。
AI 深度解读
让 AI 给现有代码补测试,可能得到一套全绿的结果,却把业务错误一起固定下来。作者提出的办法是:让测试 agent 只看业务规则和公开接口契约,再从外部验证系统是否兑现承诺。
作者所在公司尝试过这种做法,认为最终的违规报告很有用。但文中的退款案例是假设示例,完整流水线也是作者今天会采用的设计,没有对照实验或效率数据。它值得借鉴的地方,是把「测试依据从哪里来」放在了生成多少测试之前。
为什么测试全绿,业务仍然可能是错的
假设退款规则允许客户在收货后 30 天内申请退款,代码却写成了 daysSinceDelivery < 30。让 agent 阅读代码再补测试,它可能直接断言第 30 天应该被拒绝。
代码有错,测试也错,两者却能互相证明。
这里涉及测试判定依据(test oracle):观察到一个结果后,凭什么认定它正确?如果唯一依据就是实现,测试最多证明程序符合当前代码表达的逻辑,无法证明代码符合业务要求。
同一个 agent 在同一上下文里写代码和测试,问题更明显。它对需求的一次误解,会同时进入实现和断言。对于完全漏写的要求,例如「只有订单所有者可以申请退款」,代码里甚至没有线索提醒它补测。
测试的独立性,首先取决于预期结果的来源。
作者也承认,能读代码的 agent 如果同时拿到独立需求,也可以发现这些问题。代码访问权限不是唯一问题;隔离仓库,只是防止测试围着实现打转的一种直接办法。
把规则交给 agent,不能把猜答案的权力也交出去
黑盒测试并不新鲜。作者引用 ISTQB Foundation Level 大纲第 4 章中的经典方法:用等价类划分覆盖不同输入类别,用边界值分析检查 29、30、31 天,用决策表展开多条件组合,用状态迁移测试检查生命周期中的合法与非法操作。
LLM 的作用是降低展开用例的劳动成本。把 50 条规则整理成几百个精确场景很费工,模型不会写到第 140 个就嫌烦。但「能展开」不等于「能决定业务」。
文中用五条假设退款规则说明输入应该长什么样:
- BR-01:客户可以在收货后 30 天内申请退款。
- BR-02:超过 500.00 EUR 的退款,付款前必须经过经理批准。
- BR-03:数字商品下载后不可退款。
- BR-04:一个订单的累计退款不能超过实付金额。
- BR-05:只有下单客户可以申请该订单的退款。
每条规则有 ID,只表达一个可以检查的要求。测试设计 agent 再输出用例 ID、规则 ID、采用的技术、前置条件、Given/When/Then 和预期结果,同时覆盖非法输入与越权操作。
遇到规则没说清的地方,必须留下问题:30 天按自然日还是精确时长计算,采用哪个时区?部分下载算不算下载?同一订单退款两次、每次 300 EUR,是否需要审批?500.00 EUR 及以下是立即付款,还是仍需排队?
这些问题不是生成失败,而是需求缺口。
第一个人工检查点就在这里:业务负责人先审用例、回答问题,再把答案写回规则。若规则只是照着现有系统行为整理,或者测试失败后随手改预期去迎合返回值,隔离代码也保不住独立性。
从规则到报告,关键是隔离、复跑和追溯
作者建议先建立独立的 qa-workspace/,只放 rules/refunds.md、公开契约 contracts/openapi.yaml、生成用例的 cases/、Playwright 代码的 tests/,以及保存 BASE_URL 和测试凭据的 .env.test。应用仓库不克隆进来,环境文件加入 gitignore。
agent 通过 staging 的公开 API 和 UI 操作系统。靠提示词要求「不要打开 src/」,不如让源码根本不在工作区里。同一个模型可以承担开发和测试,但测试设计需要新会话、独立上下文,不能带入实现信息。
完整流程由三个角色承担:第一个设计用例,第二个生成可执行测试,第三个整理失败与证据。作者不主张让 agent 边点击边临场判断,因为修复后需要重复运行同一套检查。
下面这段原文代码,演示如何把第 30 天的退款边界落成 Playwright 测试:
import { test, expect } from "@playwright/test";
import { createDeliveredOrder, loginAs } from "./fixtures";
// TC-02 / BR-01: day 30 after delivery is still inside the refund window
test("TC-02 BR-01: refund accepted on day 30 after delivery", async ({ request }) => {
const customer = await loginAs(request, "customer-a");
const order = await createDeliveredOrder(request, customer, { deliveredDaysAgo: 30 });
const response = await request.post(`/api/orders/${order.id}/refunds`, {
headers: customer.headers,
data: { amount: order.total },
});
// BR-01 says "accepted"; 201 is how the API contract says "accepted"
expect(response.status()).toBe(201);
});
要看的是最后一行:规则决定「接受退款」,公开 API 契约决定用 201 表达接受。不能因为当前接口返回了某个状态码,就把它写成正确答案。契约没有定义,也应回到待澄清问题。
这只是示意代码。loginAs、createDeliveredOrder 需要自行提供,baseURL 来自 Playwright 配置。尤其是「收货已满 30 天」的状态,必须能通过外部接口、seed endpoint 或 fixtures 构造,否则边界测试无法落地。
UI 测试可以使用 getByRole、getByLabel 定位元素。作者也提到 Playwright 的 planner、generator、healer,以及初始化命令 npx playwright init-agents --loop=claude。但 healer 的改动必须审核,不能把应用违反规则导致的失败修成通过。
运行后,报告应列出规则、用例、预期、实际结果及响应体、截图或 trace,并提出三类判断:明确违反规则的是 Bug,规则没定义的是 Rule gap,误读规则或准备状态错误的是 Bad test。第二个人工检查点负责确认判断,再决定是否建工单。
假设报告中的第 30 天退款返回 422、两次退款累计达到订单金额的 110% 仍被接受、用户 B 退款用户 A 的订单却得到 201 Created,分别指向边界、累计金额和权限问题。两笔各 300 EUR 未审批,则暂时只能归为规则缺口。这些都是展示报告格式的假设结果。
我的判断
我赞同它把测试预期与实现分开,也赞同先审用例、再生成代码。业务人员更容易判断「这条退款场景对不对」,而不是检查几十行测试代码是否准确表达政策。
不过,这仍然是经验之谈。作者没有提供与人工测试、读代码的 agent、传统 BDD 的对照数据,也没有给出缺陷发现率、误报率和维护成本,不能据此认定黑盒 agent 一定更高效。
与 BDD 相比,它延续了行为契约和 Given/When/Then 的表达,额外强调测试生成过程中的信息隔离。与单元、集成测试相比,它更适合验证客户能观察到的业务承诺。缓存、重试、内部队列等不可直接观察的细节,仍需要其他测试;UX、性能和负载问题也不属于这套方案的主要目标。
稳定的 staging 和可控的状态准备同样是前提。环境抖动会制造失败,规则歧义会制造误报,每一条都需要人排查。隔离源码只是起点,审核过的规则和可信的测试环境才让结果有用。
能拿来做什么
- 为退款、审批、订单权限等明确业务流程整理带 ID 的规则文件,先让 agent 生成场景与待澄清问题,由业务负责人确认后再写测试。
- 已使用 Playwright 的团队,可以让独立 agent 根据获批用例和 OpenAPI 契约生成 API 或 UI 测试,在测试名里保留规则 ID,方便失败后追溯。
- 给涉及时间边界和累计操作的测试准备可重复状态,使用 staging 测试账号,并把凭据放进加入 gitignore 的环境文件。
- 在日常 AI 编程中,将开发上下文与业务验收上下文分开;收到失败报告后,先确认是实现错误、规则缺口还是坏测试,再决定改哪里。