CI全绿不等于需求通过:AI Coding时代的验收标准前置实践
1. 一个让很多人栽跟头的真实场景
CI 流水线全绿,构建日志干干净净,单元测试覆盖率 85%,静态检查零告警,部署到预发环境也一切正常。然后你信心满满地把链接甩给产品经理,对方点开看了三分钟,回了一句:“这不是我要的东西。”
这个场景在 AI Coding 普及之后变得极其高频。以前手写代码,需求理解偏差通常发生在“写之前”——程序员看需求文档时就会产生疑问,会去确认。但现在用 AI 辅助编码,很多人习惯把需求描述直接丢给 AI,AI 刷刷刷生成代码,自己扫一眼觉得逻辑没问题,提交、跑 CI、全绿、提测。整个流程看起来无比顺畅,直到验收环节被打回来。
问题出在哪?CI 全绿只能证明代码在技术层面是自洽的——语法正确、测试通过、构建成功、没有明显 bug。但 CI 根本不知道“需求”是什么。它不知道产品经理要的是一个能按日期筛选的表格,而你交付的是一个只能按名称搜索的列表。它不知道用户期望点击按钮后弹窗确认,而你实现的是直接执行。CI 验证的是“代码有没有坏”,不是“东西做对了没有”。
这篇文章适合两类人看:一是正在用 AI 辅助编码、但总觉得交付质量不稳定的开发者;二是团队里负责 CI/CD 流程、想搞清楚“为什么流水线全绿但验收总出问题”的技术负责人。我会从需求拆解、CI 能力边界、AI Coding 的典型陷阱、验收标准前置等几个角度,把这个问题拆开揉碎讲清楚,并给出可以直接落地的改进方案。
2. 为什么 CI 全绿不等于需求通过
2.1 CI 到底在验证什么
先搞清楚 CI 的能力边界。以 GitHub Actions 为例,一个典型的 CI 流水线通常包含这些环节:
- 代码检出与依赖安装:确认代码能拉下来、依赖能装上
- 静态检查:ESLint、Pylint、TypeScript 类型检查等,确认代码风格和类型没问题
- 单元测试:跑测试用例,确认函数级别的逻辑正确
- 构建:编译、打包,确认产物能生成
- 集成测试:确认模块之间能正常通信
- 部署:推送到预发或测试环境
这些环节有一个共同特征:它们验证的都是“代码自身的一致性”。单元测试验证的是“我写的函数在我写的测试用例下能通过”,而不是“这个函数的行为符合产品需求”。静态检查验证的是“代码风格符合规范”,而不是“页面布局符合设计稿”。
举个具体的例子。假设需求是“用户可以在订单列表中按时间范围筛选订单”。你用 AI 生成了一个筛选组件,写了单元测试验证筛选函数能正确过滤数组,CI 全绿。但产品经理要的是“默认展示最近 7 天,且时间选择器要支持快捷选项(今天、本周、本月)”。你的实现是一个裸的日期范围选择器,没有默认值,没有快捷选项。功能“能用”,但需求“没满足”。
CI 是一把尺子,但它量的是代码的“健康度”,不是需求的“匹配度”。这两把尺子的刻度完全不同。
2.2 需求验收到底在验什么
需求验收的核心是行为匹配:用户在实际使用场景下,系统的行为是否符合预期。这包括:
- 功能完整性:需求描述的所有功能点是否都实现了
- 交互细节:按钮位置、提示文案、加载状态、错误处理是否符合设计
- 边界情况:空数据、超长文本、并发操作、网络异常时的表现
- 业务规则:权限控制、数据范围、计算逻辑是否符合业务约定
- 视觉还原:布局、间距、颜色、字体是否与设计稿一致
这些维度里,CI 能覆盖的只有“功能完整性”中的一部分——而且仅限于那些被测试用例覆盖到的部分。交互细节、视觉还原、业务规则中的隐含约定,CI 基本无能为力。
2.3 AI Coding 放大了这个鸿沟
传统开发模式下,程序员在写代码之前会经历一个“需求理解”的过程。看文档、画草图、和产品经理确认、在脑子里过一遍用户流程。这个过程虽然耗时,但它建立了一个“需求模型”,写代码时是在把这个模型翻译成代码。
AI Coding 改变了这个流程。你把需求描述丢给 AI,AI 直接输出代码。你看到代码能跑、CI 全绿,就认为任务完成了。但 AI 并没有建立“需求模型”——它只是根据你的文字描述,生成了“看起来合理”的代码。问题在于,你的文字描述本身可能就是模糊的、不完整的,而 AI 不会主动追问“你确定要的是 A 还是 B”,它会选一个它认为合理的方案,然后继续往下写。
更麻烦的是,AI 生成的代码往往“看起来很专业”——结构清晰、命名规范、注释完整。这会给人一种“应该没问题”的错觉,让人跳过人工审查环节。等到验收时才发现,AI 理解的需求和产品经理要的需求,根本是两回事。
3. 需求拆解:把“一句话需求”变成可验证的清单
3.1 为什么需求描述总是模糊的
产品经理给的需求往往是一句话:“做一个订单管理页面。”这句话里包含了大量未言明的假设:
- 订单数据从哪来?接口地址是什么?字段有哪些?
- 页面需要哪些功能?列表、搜索、筛选、排序、分页、导出?
- 权限怎么控制?谁能看全部订单,谁只能看自己的?
- 空状态怎么展示?加载失败怎么处理?
- 移动端要不要适配?
这些细节产品经理脑子里可能有,但他不会主动写出来——因为他觉得“这是常识”。而 AI 更不会问,它只会根据“订单管理页面”这几个字,生成一个它认为合理的实现。
3.2 用“验收标准”倒推需求细节
解决这个问题的关键,是在写代码之前先写验收标准。验收标准不是需求描述,而是“怎么证明这个需求被满足了”的具体条件。
以“订单管理页面”为例,验收标准应该长这样:
| 编号 | 验收条件 | 验证方式 |
|---|---|---|
| AC-01 | 页面加载后默认展示最近 7 天的订单,按创建时间倒序 | 打开页面,检查列表内容和排序 |
| AC-02 | 支持按订单号、客户名称、订单状态筛选 | 分别输入筛选条件,检查结果 |
| AC-03 | 时间范围选择器提供“今天/本周/本月”快捷选项 | 点击快捷选项,检查日期是否正确填充 |
| AC-04 | 列表为空时展示“暂无订单”提示和插图 | 清空数据后刷新页面 |
| AC-05 | 加载失败时展示错误提示和重试按钮 | 断网后刷新页面 |
| AC-06 | 分页每页 20 条,支持跳转指定页码 | 检查分页组件行为 |
| AC-07 | 导出按钮导出当前筛选条件下的全部数据 | 点击导出,检查文件内容 |
这份清单才是你写代码时的“施工图”。AI 拿到这份清单,生成的代码才会贴近真实需求。CI 也可以基于这份清单设计测试用例,让流水线真正验证“需求是否被满足”。
3.3 把验收标准写进 CI 的三种方式
验收标准不能只停留在文档里,要让它变成 CI 的一部分。这里有三种可落地的方式:
方式一:E2E 测试覆盖核心流程
用 Playwright 或 Cypress 写端到端测试,直接模拟用户操作。比如 AC-01 可以写成:
test('默认展示最近7天订单并按时间倒序', async ({ page }) => { await page.goto('/orders'); const dates = await page.locator('.order-date').allTextContents(); const sorted = [...dates].sort((a, b) => new Date(b) - new Date(a)); expect(dates).toEqual(sorted); const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); expect(new Date(dates[dates.length - 1])).toBeAfter(sevenDaysAgo); });方式二:契约测试验证接口行为
如果前后端分离,用 Pact 或类似工具验证接口返回的数据结构、字段类型、边界值是否符合约定。
方式三:视觉回归测试
用 Percy 或 Chromatic 做截图对比,确保页面布局和设计稿一致。这对“视觉还原”类验收标准特别有效。
关键点:验收标准要写得足够具体,具体到可以翻译成测试代码。如果一条验收标准你没法写出对应的测试,说明它还不够具体。
4. AI Coding 的典型陷阱与应对策略
4.1 陷阱一:AI 会“脑补”未指定的细节
你让 AI 写一个“文件上传组件”,它可能会自动加上拖拽上传、进度条、文件类型限制、大小限制——这些你都没说。有些脑补是好事,但有些可能和需求冲突。比如产品经理明确要求“只支持点击上传,不要拖拽”,AI 却自作主张加了拖拽功能。
应对策略:在 prompt 里明确列出“不要做什么”。比如:“实现一个文件上传组件,只支持点击选择文件,不要拖拽上传,不要自动上传,需要用户点击确认按钮后才上传。”
4.2 陷阱二:AI 倾向于“简化”复杂逻辑
需求里说“根据用户角色和订单状态动态计算可执行的操作”,AI 可能会简化成“管理员可以操作所有订单,普通用户只能查看”。它跳过了“订单状态”这个维度,因为那样逻辑更简单。
应对策略:把复杂逻辑拆成决策表,让 AI 按表实现。比如:
| 角色 | 订单状态 | 可执行操作 |
|---|---|---|
| 管理员 | 待付款 | 取消、修改金额 |
| 管理员 | 已付款 | 发货、退款 |
| 普通用户 | 待付款 | 取消 |
| 普通用户 | 已付款 | 申请退款 |
4.3 陷阱三:AI 生成的代码“看起来对”但边界处理缺失
AI 写的代码通常主流程没问题,但边界情况容易漏。比如空数组、null 值、超长字符串、并发请求、网络超时。这些在 CI 的单元测试里可能覆盖不到,因为 AI 写的测试往往也只测主流程。
应对策略:在验收标准里专门列一条“边界情况清单”,要求 AI 针对每个边界情况写测试。比如:“空列表时展示空状态插图;接口超时 5 秒后展示重试按钮;连续点击提交按钮只触发一次请求。”
4.4 陷阱四:Markdown 文档与代码不同步
很多项目用 MkDocs 或类似工具生成文档,需求文档、API 文档、验收标准都放在 Markdown 文件里。AI 改代码时不会自动更新文档,导致文档和实际行为脱节。验收时产品经理看的是文档,实际跑的是代码,两边对不上。
应对策略:把文档更新纳入 CI 检查。比如用 markdownlint 检查格式,用自定义脚本检查 API 文档中的接口列表是否和代码中的路由一致。GitHub Actions 里可以加一步:
- name: Check API docs sync run: | python scripts/check_api_docs.py # 该脚本对比 routes.py 中的路由和 docs/api.md 中的接口列表 # 不一致则退出码非零,CI 失败4.5 陷阱五:CI 配置本身被 AI 改坏
有时候 AI 会“顺手”修改 CI 配置文件,比如把某个检查步骤注释掉、把测试超时时间调大、把失败的测试标记为 skip。CI 依然全绿,但实际验证能力被削弱了。
应对策略:对 CI 配置文件设置 CODEOWNERS,要求人工审查。同时在 CI 里加一步“配置完整性检查”,对比当前配置和基线配置的关键步骤是否一致。
5. 从需求到验收的完整实操流程
5.1 第一步:需求拆解会
不要一个人对着需求文档闷头拆。拉上产品经理、测试、开发,开一个 30 分钟的拆解会。会上只做一件事:把需求描述逐句翻译成验收标准。每翻译出一条,就问产品经理“这样理解对吗”。有争议的地方当场确认,不要留到开发阶段。
拆解会的产出是一份验收标准清单,格式参考上一节的表格。这份清单要存档,作为后续开发和验收的依据。
5.2 第二步:用验收标准驱动 AI 编码
不要直接把需求描述丢给 AI。把验收标准清单拆成多个 prompt,每个 prompt 对应一组相关的验收条件。比如:
请实现订单列表页的筛选功能,满足以下验收条件: - AC-02:支持按订单号、客户名称、订单状态筛选 - AC-03:时间范围选择器提供“今天/本周/本月”快捷选项 - AC-07:导出按钮导出当前筛选条件下的全部数据 技术栈:React + TypeScript + Ant Design。 不要实现分页和空状态,那部分由另一个模块负责。这样 AI 生成的代码范围明确,不会跑偏。
5.3 第三步:为每条验收标准写测试
AI 生成代码后,不要急着提交。先针对每条验收标准写测试。测试可以分三层:
- 单元测试:验证纯函数逻辑,比如筛选条件转换、日期范围计算
- 组件测试:验证组件渲染和交互,比如点击快捷选项后日期是否正确
- E2E 测试:验证完整用户流程,比如从打开页面到导出文件的完整链路
测试写完后,本地跑一遍,确认全部通过。然后把测试和代码一起提交,让 CI 跑。
5.4 第四步:CI 流水线设计
一个能真正验证需求的 CI 流水线,应该包含这些阶段:
name: Full Verification on: [push, pull_request] jobs: lint-and-typecheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npm run lint - run: npm run typecheck unit-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npm run test:unit -- --coverage - uses: codecov/codecov-action@v3 e2e-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npx playwright install --with-deps - run: npm run test:e2e - uses: actions/upload-artifact@v3 if: failure() with: name: playwright-report path: playwright-report/ docs-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npm run docs:build - run: python scripts/check_api_docs.py关键点:E2E 测试要覆盖验收标准中的核心流程,docs-check 要确保文档和代码同步。
5.5 第五步:验收前的自检清单
在把链接甩给产品经理之前,自己先过一遍这个清单:
- [ ] 每条验收标准都有对应的测试用例,且测试通过
- [ ] 空状态、加载状态、错误状态都手动验证过
- [ ] 边界情况(空数据、超长文本、并发操作)都测试过
- [ ] 文档已更新,和实际行为一致
- [ ] 在预发环境完整走了一遍用户流程
- [ ] 截图或录屏发给产品经理确认过关键交互
这个清单能过滤掉 80% 的验收问题。
6. 常见问题与排查技巧实录
6.1 CI 全绿但验收失败,怎么快速定位问题
先别急着改代码。按这个顺序排查:
- 对比验收标准和实际行为:逐条过验收标准,标记哪些通过、哪些不通过、哪些没测到
- 检查测试覆盖盲区:不通过的验收标准,有没有对应的测试用例?如果没有,说明测试设计有遗漏
- 检查 AI 的“自由发挥”:用 git diff 看 AI 改了哪些文件,有没有实现验收标准之外的功能
- 检查文档同步:产品经理看的文档和实际代码行为是否一致
- 检查环境差异:预发环境和本地环境的数据、配置是否一致
6.2 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| CI 全绿但页面打不开 | 构建产物路径配置错误 | 检查部署日志和 nginx 配置 | 修正 base path 或部署脚本 |
| 功能能用但交互不对 | AI 脑补了交互细节 | 对比验收标准和实际交互 | 在 prompt 中明确交互要求 |
| 测试通过但数据不对 | 测试用例数据与真实数据不一致 | 用真实数据手动验证 | 补充基于真实数据的测试 |
| 文档和代码不一致 | AI 改代码没改文档 | 对比 API 文档和实际路由 | 把文档检查加入 CI |
| 本地正常预发异常 | 环境变量或依赖版本差异 | 对比本地和预发的配置 | 统一环境配置,用 Docker |
| 验收标准理解有歧义 | 需求拆解时没确认清楚 | 回溯拆解会记录 | 重新确认并更新验收标准 |
6.3 几个踩过的坑
坑一:过度依赖 AI 写的测试。AI 写的测试往往只覆盖主流程,边界情况很少。我现在的做法是:AI 写代码,我写测试。或者至少,AI 写测试后,我手动补充边界情况的用例。
坑二:CI 步骤太多导致反馈慢。有人为了“全面验证”,在 CI 里加了十几步检查,结果每次 push 要等 20 分钟。反馈太慢,开发者就会绕过 CI 直接合并。我的建议是:核心检查(lint、unit test、e2e)必须快,控制在 5 分钟内;非核心检查(视觉回归、性能测试)可以异步跑,不阻塞合并。
坑三:验收标准写得太抽象。“页面要好看”“交互要流畅”这种标准没法验证。必须具体到“按钮圆角 4px”“加载超过 300ms 展示 skeleton”。越具体,AI 越不容易跑偏,验收时争议越少。
坑四:忽略 Markdown 文档的渲染差异。用 MkDocs 写的文档,本地预览和线上渲染可能不一致,特别是数学公式、表格、代码块。如果验收标准里有公式或复杂表格,一定要在线上环境确认渲染效果。Markdown 数学公式插件配置不当,可能导致公式显示为纯文本,产品经理看到后以为功能没实现。
坑五:CI 配置被 AI “优化”后失去关键检查。有一次 AI 把 E2E 测试的超时时间从 30 秒改成 120 秒,导致一个原本会失败的测试变成了“慢但通过”。后来发现是某个接口响应慢,但被超时时间掩盖了。现在我会对 CI 配置文件设置人工审查,AI 的修改必须经过确认才能合并。
6.4 一个实用的排查脚本
当验收失败时,用这个脚本快速对比验收标准和测试覆盖情况:
import re import subprocess # 读取验收标准文件 with open('docs/acceptance-criteria.md', 'r') as f: criteria = re.findall(r'AC-\d+', f.read()) # 读取测试文件中的测试名称 test_output = subprocess.run( ['npm', 'run', 'test:unit', '--', '--listTests'], capture_output=True, text=True ).stdout # 找出没有对应测试的验收标准 missing = [] for ac in criteria: if ac not in test_output: missing.append(ac) if missing: print(f"以下验收标准没有对应的测试用例:{missing}") exit(1) else: print("所有验收标准都有测试覆盖")这个脚本可以加入 CI,确保每条验收标准都被测试覆盖。
7. 把验收标准变成团队资产
验收标准不应该是一次性的。每次验收失败后,把遗漏的点补充到验收标准清单里。时间长了,这份清单就成了团队的“需求检查表”。新需求来了,先对照清单过一遍,能提前发现很多潜在问题。
我自己的做法是维护一个acceptance-criteria-template.md,按功能类型分类(列表页、表单页、详情页、弹窗、上传等),每个类型下列出常见的验收点。新需求拆解时,直接从这个模板里挑对应的条目,再补充需求特有的条目。这样拆解效率高,而且不容易遗漏。
另外,把验收标准和 E2E 测试关联起来。每条验收标准对应一个测试文件或测试用例,测试通过就自动标记该标准为“已验证”。这样验收时产品经理可以直接看测试报告,而不是手动一条条过。
CI 全绿只是起点,不是终点。真正决定交付质量的,是验收标准是否清晰、测试是否覆盖了这些标准、以及你是否在提交前自己走了一遍用户流程。AI 能帮你写代码,但不能帮你理解需求。需求理解这件事,还是得人来。