01 篇立论,02 篇立规范:三层 SSOT(Single Source of Truth,唯一事实来源),即需求层(L0)、事实层(L1)、流程层(L2),加一份带治理规则的入口 README,“什么是对的、住哪、谁把控、怎么改”有了唯一答案。但上一篇只回答了 GSDD(Governed Spec-Driven Development,治理规范驱动开发)三件事里的第二件,事实烂没烂;本篇是系列的证据篇,回答第三件,也是最难被替代的一件:凭什么信。它对应四支柱的第三根:验收体系。
重构的产物,是一张三层单向引用的结构:acceptance(验收标准)定“验什么”,testcase(剧本)定“怎么验”,代码库 tests/ 定“怎么自动跑”。先论证旧结构为什么必须推翻,再逐层给出结构,最后回答那个老问题:TDD 还剩多少,剩哪几块。
一、为什么验收要重构
传统开发里,测试与实现之间隔着一段时差,常常还隔着人手。TDD 把这种隔离推到极致:测试先于实现存在,断言先写下“什么算对”,代码后到,只为让断言变绿。这个次序是测试全部公信力的来源:标准不是看着答案拟的。
AI 时代,这个次序塌了。代码是 AI 写的,测试也是 AI 写的,而且多半出自同一次生成:实现落地,测试顺手跟着落地,断言照着实现的形状写。答卷人自己出卷。
这时“测试全绿”证明的是什么?只证明一件事:代码与测试一致。而两者同源,同一只手、同一份上下文。实现若按某个旧口径写下一条判定,测试就会按同一个旧口径断言这条判定:口径错了,两边一起错,绿灯照样亮。一致性不是正确性;两边同源的时候,一致性连独立信息都提供不了。逐行审查也补不上这个洞,01 篇已经说过,生成速度早就把逐行人审甩开了。
把次序倒过来也无济于事:TDD 式的先写测试、再写实现,次序换了,来源没换,AI 写测试的依据,仍然是它对需求的猜测。用猜测验证猜测,两边一致只说明自洽,不说明猜中了你要什么。判卷的全部价值来自一件事:标准独立于被验证对象。标准一旦出自被验证对象之手,结论只有一句:
被验证对象能影响验证标准,就什么也保证不了。
被验证对象是代码,验证标准是测试;测试由 AI 按着代码写,等于被验证对象参与了标准的制定,绿灯从此不再是证据,只是回声。
出路只有一条:验证标准必须住在被验证对象够不着的地方,由人写定、住在 SSOT、AI 只读。这不是对 AI 的道德怀疑,是纯结构判断:任何被验证对象都不能参与定标准,这条规则对人同样成立。领英智能触达那六周是多重失守的结果,验收标准缺位就是其中之一,每一轮修复算不算“修好了”,全凭当场跑一遍的感觉,没有一个写下来的标准可以对质。
01 篇结尾有一句承诺:会消失的是逐行看代码,不能消失的是独立验收。本篇就是“独立验收”的施工图。
二、三层单向引用
先消一个歧义:本篇的“三层”指验收链的三段,04 篇叫它三层验收,与 02 篇的三层(需求层/事实层/流程层)是两回事,别混。
重构后的验收体系是三层,箭头只有一个方向:

acceptance.md,定“验什么”。住在 ssot/L2-process/,流程层。“系统什么算对”在验收意义上的全部答案,只在这一份文件里。它不知道下面两层的存在,它只定义标准,不关心谁来执行。
testcase.md,定“怎么验”。同样住在流程层。它把散落的验收项串成完整的业务流程走查:先走哪步、后走哪步、每步核对哪条验收项。它引用验收项编号,自己不定义任何新标准。
代码库 tests/,定“怎么自动跑”。acceptance 与 testcase 住在 ssot/,由程序员把控;tests 住代码库,是 AI 的派生物。至于通过 harness 设置、明确 SSOT 修改权限归人管的那层配置,本篇不登场,它是 04 篇的主角。标准在人手里,跑法归 AI。
只能自上而下引用,不许反向定义。这条禁令有两个方向。向上看:testcase 不许发明 acceptance 里没有的检查点,tests 不许出现 acceptance 里没有的标准。AI 写测试时“顺手补一个断言”,就是在代码库里私自定标准,标准从此有了第二个出处;下层发现上层缺了东西,正规通道只有一条:回 acceptance 补验收项(程序员改,流程层门槛),拿到编号再引用。向下看:acceptance 不许依赖下层,标准文本里不许出现“见 tests/export.spec.ts”这类话,标准必须独立于实现成立,测试怎么重构、框架怎么换,标准一个字不动;写反了,标准就被实现劫持。反向引用一旦放行,第一节的判据立刻从后门回来:AI 写下的测试,成了它自己要满足的标准。02 篇 README 头部立的原则①,同一事实/标准只在一处定义,其余地方只引用,在验收侧的落实,就是这条单向引用。
这套结构自己也得过 01 篇给护栏立的四问关卡,答不上实现细节的结构是愿望:
- 哪个工具:三份东西,
ssot/L2-process/acceptance.md、ssot/L2-process/testcase.md、代码库tests/目录,外加贯穿三层的验收项编号。 - 执行点在哪:开发 session 里,AI 先读剧本与验收项再动手;验收执行时,沿剧本走查、跑
tests/;人审的是编号对账与证据。 - 判定数据是什么:验收项编号(唯一引用键)、每条验收项要求的证据(日志/截图/测试结果)、测试运行结果。
- 失败了会怎样:编号对不上,该测试或该步骤不成立,验收判不通过;测试红灯,对应验收项未过,交付阻断;要改标准,唯一入口是改 acceptance(走 02 篇的改动门槛),实现跟着编号改。
后三节把三层逐个拆开。
三、acceptance:程序员手写的验收标准
acceptance 是程序员手写的自然语言 markdown,写行为,不写代码。位置 ssot/L2-process/acceptance.md,作者与维护者是程序员,改动走流程层门槛(02 篇:程序员日常维护);AI 在任何开发 session 里对它只读。
一份 acceptance 由三件东西组成:验收项、行为矩阵、证据要求。
验收项,带编号。一条验收项是一句可判定的业务断言:懂业务的人读完,能不含糊地给出“过”或“不过”。编号形式不重要(A1 也罢、AC-07 也罢),重要的是稳定、可被引用,编号是整条引用链的通货:剧本串它,测试挂它,证据按它对账。例如:
A3 无导出权限的角色请求导出报表时,系统返回 403,不产生任何导出文件,并在审计日志留下一条拒绝记录。
验收项不限于功能行为。“10 万条记录 30 秒内导出完毕”这类性能断言、“拒绝操作必须留痕且不可篡改”这类安全断言,同样是可判定的验收项,证据相应换成压测报告、审计日志,编号与引用纪律分毫不差。
行为矩阵。一句自然语言的标准,常在“什么角色、什么数据、什么状态下该怎样”上留歧义;行为矩阵把一组验收项展开成逐行判定,编号 × 条件 × 预期行为 × 证据,一行一个格子。本文示例组“导出权限”展开如下(A3 即上面那条):
| 编号 | 条件 | 预期行为 | 证据 |
|---|---|---|---|
| A1 | 管理员,导出全量 | 200,文件含全部有效记录 | 导出文件 + 记录数核对日志 |
| A2 | 成员,导出自己权限范围 | 200,文件只含本人范围数据 | 导出文件 + 记录数核对日志 |
| A3 | 成员,请求他人范围数据 | 403,不生成文件,审计留痕 | API 日志截图 + 审计记录 |
| A4 | 未登录请求 | 401,不生成文件 | API 日志截图 |
矩阵的价值在验收执行时兑现:AI 逐行核对、逐行留证,人逐行确认,验收从一种感觉,变成一张清单。写作侧它还有一功:逼出组合场景。“权限不足”“重复提交”这类分支,散文式验收项最容易漏写;表格一格一格摆出来,缺一格一眼可见。哪一格指不回编号,说明验收项缺了,先补 acceptance,再谈实现。
证据要求。每条验收项写明执行验收时必须留下什么证据,日志、截图、测试结果。这一栏是 01 篇第三件事(人审验收证据,不审代码)落到纸面的形态:人审的对象不是 diff,是证据。证据本身随代码库与 CI 留存,不进 ssot/(目录约定 04 篇给出),02 篇的布局里没有证据文件的位子,SSOT 只放规范,不放证据。
AI 与 acceptance 的职权边界一句话:AI 只执行验收并产出证据,无权自造或改写验收项。执行验收,是沿剧本走查、跑测试、截日志、截图;产出证据,是把这一轮的日志、截图、测试结果按验收项编号组织好,交给人。人审的读法随之确定:逐个编号,核对“这个编号下的证据是否支撑这条验收项的描述”。对不上号只有两种处理:证据不足,AI 继续修,修到证据成立;验收项本身定错了,程序员改 acceptance,重新验。AI 没有第三种选项,它不能顺手改标准,让证据变得好看。自造或改写,一个编号都不行;AI 认为标准该加一条,正确动作是起草一条、交人采纳,01 篇已把分寸说清,AI 可以起草,人采纳了才算数。机械保障靠 harness 设置:acceptance 住 SSOT,AI 全层只读,02 篇的治理规则,04 篇落成写保护。本篇先把职权说死:验收项的笔,在程序员手里。
为什么坚持自然语言:acceptance 的第一读者是人,第二读者才是 AI。它是人写、人审的判定书,可读性优先于可执行性;可执行的那一份从它派生,那是下一层的事。标准只有一份,形态分两层:规范层的文本定义“验什么”,实现层的代码解决“怎么自动跑”。
四、testcase:流程即用例的剧本
testcase(ssot/L2-process/testcase.md)是业务流程检验剧本:多步骤走查,以检验业务流程为导向。
流程即用例。传统测试用例以函数或模块为单位,“测导出接口的权限分支”;剧本以业务流程为单位,一条用户能从头讲到尾的完整路径。一条剧本长这样:
剧本《成员数据导出》
- 管理员准备全量示例数据。
- 成员 B 登录,录入两条自己权限范围内的记录。
- 成员 B 发起导出,核对文件只含这两条。(A2)
- 用成员 B 的会话请求全量导出,核对 403、无文件、审计留痕。(A3)
- 管理员发起导出,核对文件含全量数据。(A1)
每一步按所引验收项的证据要求留证。用例就是流程本身:剧本走完一遍,几条验收项就在真实业务顺序里各被核对一次。这比孤立用例多验出一类问题,每条验收项单独都过、串成流程却走不通的那类。业务错误最爱藏在接缝处:每一步单看都对,串起来不对。
剧本不定义新标准,只串联验收项编号。剧本里不允许出现 acceptance 里没有的预期行为;走查中发现流程需要一个新的判定,正确动作是回 acceptance 加验收项、拿到编号,再回剧本引用,顺序不能反。这条纪律保证“验什么”永远只有一个出处:标准住在 acceptance,剧本是流程视图。
AI 开发前读剧本理解业务。剧本在开发侧的作用容易被低估:开发 session 的第一步不是领任务,是把相关剧本读一遍,这条流程从哪开始、经过哪些角色、在哪一步应当被拦下。01 篇的时代注脚在此兑现:AI 只知道它读到的规范,规范没写的部分它只能猜。剧本是业务的最低限度书面形态;AI 交出的不是“一个能用的导出接口”,而是“剧本第 3 步能走通的那个环节”。同一份文件,开发前当业务说明读,开发后当验收脚本走。
走查失败的处理也定死:某一步期望不符,停在该步,留证据,报告到编号粒度,“剧本《成员数据导出》第 4 步未过,涉及 A3”。不许跳步,不许顺手改剧本。慢测试转手动之后(第六、七节),剧本同时是手动走查的操作单:照着走,照着留证。手动走查的证据与自动证据同待遇:按编号归档,谁执行的、什么时候、结果如何,一并留存。
五、tests:测试是验收项的实现
第三层回到代码库。tests/ 里的自动化测试,定位是验收项的实现:acceptance 定义“验什么”,测试代码负责这条标准“怎么自动跑”。
强制引用编号。每个自动化测试必须标注它实现的是哪条验收项、哪段剧本,编号落在测试文件头部或用例名里,具体到“测试代码里必须出现编号字样”:
// 实现:acceptance A3(剧本《成员数据导出》步骤 4)
test('A3 权限边界:成员请求他人范围数据,返回 403', async () => { /* ... */ });机制不挑框架,也不挑测试类型,单元也好、接口也好、端到端也好,绑的不是写法,是编号:每个测试必须指得回一个验收项编号,指不回去的,不算验收体系的成员,它的红与绿对验收没有发言权。一个验收项可以由多个测试共同实现,条件、边界、分支各测一角,编号都指向同一个验收项。
无权新增或改写。AI 写测试时觉得“还有一种情况该测”,不能直接加一个引用不到编号的测试,那是反向定义。正确动作:回 acceptance 起草验收项,人采纳、编号落地,再补实现;程序员自己想加个顺手的小测试,走的是同一条通道,先有验收项,哪怕只有一行。这条纪律顺手产出一张对账表:acceptance 的编号清单,对上 tests/ 的编号清单,哪些验收项已有自动化实现、哪些还只停在剧本走查,一眼数清。它不产出行覆盖率的百分比,产出一张更硬的表,判定数据是编号,不是统计值。
测试不是第二出处。测试断言里写死的预期值与 acceptance 冲突时,错的是测试或实现,永远不会是 acceptance,仲裁一律回 SSOT。行为变化要找合法出口,只有一条路:改 acceptance(走改动门槛),波及的剧本与测试跟着编号改。这把一类高频争议一次性了结:“测试是这么写的,所以行为没错”,方向反了:验收项是这么写的,所以测试该这么断言。
失败语义也随之归位:CI 红灯亮起,程序员看到的第一信息不是“哪行代码错了”,而是“哪个验收项没过”,按编号翻证据、翻剧本、翻标准,读的是标准与证据的对照,不是逐行代码。红灯不是“代码质量问题”,是“对应验收项未通过”,交付阻断;处理顺序遵循 02 篇 README 头部的原则②(先改规范再改实现):先弄清是验收项定错了还是实现做错了,两种修复都从 acceptance 的那一行开始。
flaky 也有归属。测试红灯,先复核再定性:复跑即过、查实是环境噪声的,留一条记录,不算这轮失败;测试自身摇摆不定的,当场修复或重写,不许挂起跳过。挂起的 flaky 等于给对应验收项发了长期豁免,豁免没走过 acceptance 的门槛,是私开的第二出处。
六、TDD 收窄
01 篇结尾留了个问题:单元测试的去留。答案是收窄,不是取消。收窄到哪,三条边界。它们不是三条零散偏好,而是同一件事的三个切面:测试点在 SSOT 定流程时同时定下。程序员写下每条验收项的那一刻,这条验收走哪条守线就定了,证据要求里要“测试结果”的项,就是自动化点;要“截图/日志”的项,落进剧本走查。TDD 的直觉(标准先行)在这里活了下来,只是先行的不是测试代码,是验收标准。
第一,业务规则必须测。判定标准不是“这条规则重不重要”,而是“它住在哪”:凡是写成验收项的业务规则,权限判定、状态迁移、扣款口径这类,必须有编号对应的自动化测试。A3 住在 acceptance 里一天,tests/ 里就得有引用 A3 的测试一天。“这条规则当前还成立吗”,永远不该靠人肉回忆。
第二,UI 文案与样式不进自动化。按钮上的字、间距、颜色,不写成自动化断言,改个按钮文案不该红 CI。注意这不是“不验”:这类验收项照写照执行,验证手段换成证据,执行验收时的截图,人审截图。账很好算:这是变更频率最高的一层(业务上天天调),自动化收益最低(一眼可判),维护成本最高(最脆的断言恰恰长在这里)。视觉回归工具看似能救,但基线图的更新与评审一样落在每次 UI 变更上,把像素基线当标准,等于又把标准写进了实现代码里。
第三,快速冒烟自动化,慢测试手动或定期触发。分层标准不是“重不重要”,是“跑多久”。秒级到分钟级的单元与核心接口冒烟,每次改动自动跑,守住快速反馈;分钟级以上的全流程 E2E、跨系统联动,不进每次提交的回路,转手动触发或定期触发(每夜、每周、发版前)。被移出自动化的检验项一条不删,它们仍住在剧本里,只是执行方式从自动换成手动。检验没有缩水,跑法分了层。
第三条不是技术判断,是成本判断。下一节算这笔账。
七、多用户 E2E 与 CI 成本
先说一条第一周就必须搭的基建:多角色测试账号与 token。真正要验的业务流程大多跨角色:管理员配置、成员操作、越权者被拒。第四节那条剧本,一个账号走不完,A3 从定义上就需要“有权限的”与“没权限的”两个身份同时在场。所以多角色测试账号与 token 基建第一周搭(04 篇第一周清单里它是独立一项:默认搭,没有跨角色验收项的项目可跳过):每个角色一套固定的测试账号与凭据,token 有统一的注入入口,E2E 随取随用,不临场凑号。为什么排第一周:它是角色类验收项能否自动执行的前提,基建缺位,这类验收项定得再好,也只能长期停在手动,acceptance 里写好的测试点落不成自动实现;中途再补,等于回头改所有已有测试的登录方式。
再算慢测试的账。慢测试转手动,不是因为做不到全自动,而是全自动的账算不过来。两个真实痛点:
- 大项目 CI 一轮 15 分钟起步。全量套件塞进每次提交的回路,反馈延迟就是 15 分钟;提交频率越高,等待成本按次数乘出来。AI 时代提交频率比人肉时代高一个量级,15 分钟的回路会把“改一下、验一下”的节奏整个卡死,团队开始攒一批提交再跑,批量坏味道一进来,定位成本反而翻倍。CI 从保障变成阻塞项。
- GitHub 托管 CI 的付费额度不够用。按分钟计费,全量 E2E 每次提交都跑,额度烧穿只是时间问题;烧穿之后,要么掏钱升级,要么排队等 runner,两个选项都不产生价值。停跑的测试是纯负债:它存在过,给过人安全感,然后悄悄失效。

所以结论是:快慢分层是成本决策,不是技术妥协。把慢测试移出每次提交的回路,不是对质量让步,是把预算花在刀刃上。检验强度不由“跑得多自动”决定,由 acceptance 与剧本决定,标准一条不少,贵的跑法(15 分钟的全量、烧额度的 E2E)按需触发:定期夜里跑全量剧本走查,早上看报告,发布前手动触发关键剧本;便宜的跑法(秒级冒烟)常驻回路。把“全自动”当信仰的团队,买到的是等额度的队列;把“分层”当纪律的团队,买到的是每次改动秒级反馈,外加每个周期一次的全量走查。
把本篇收个尾。“凭什么信”落成一张三层结构:acceptance(验收标准:验什么,唯一出处,程序员手写、验收项带编号)→ testcase(剧本:怎么验,流程即用例,只串联编号)→ tests(实现:怎么自动跑,强制引用编号)。下层只引用,上层不依赖;标准只住在人手里,AI 从头到尾只做两件事,按标准实现,按标准交证据。TDD 收窄成三句:业务规则必须测,测试点在 SSOT 定流程时同时定下;UI 文案与样式不进自动化,截图证据顶上;快速冒烟自动跑,慢测试手动/定期触发,多角色账号基建第一周搭。程序员盯 SSOT,不盯代码,落在验收这件事上,就是人盯验收项与证据,机器跑绿灯。
四支柱的第三根“验收体系”立完了。还差最后一根:SSOT 只读、写保护、变更拦截怎么落成硬保障,运行时怎么装起来,第一周按什么顺序把这套东西跑起来,是 04 篇的事。