# 怎样写出 AI 代理能够验证的验收标准

Canonical: https://specscore.md/zh-cn/articles/acceptance-criteria/

更新于 2026年11月3日。

[验收标准](https://specscore.md/acceptance-criteria-specification)（AC，acceptance criteria）是一项变更要算作完成之前必须为真的条件。Given/When/Then 是 [Gherkin](https://cucumber.io/docs/gherkin/reference/) 里的步骤形态。[Cucumber](https://cucumber.io/docs/bdd/) 用这种语言做行为驱动开发（BDD，behaviour-driven development）：Given 是起始状态，When 是发生一个动作，Then 是随后出现的可观察结果。产品需求文档（PRD，product requirements document）是一段散文，点出问题和打算做的产品。[规范驱动开发](https://specscore.md/zh-cn/spec-driven-development/)（SDD，spec-driven development）把结构化的规格，而不只是那段散文，当作核对实现的来源。[EARS](https://alistairmavin.com/ears/)（Easy Approach to Requirements Syntax）用来约束一条需求句子，让其中的子句保持固定顺序。持续集成（CI，continuous integration）是对每一次提议的变更所做的自动检查；本站在 [验证器页面](https://specscore.md/zh-cn/validators/) 上说明 SpecScore 的两项检查。本页说明如何写出人工智能（AI）编程代理能够验证的验收标准，并给出验收标准示例，以及 SpecScore 命令行界面（CLI，command-line interface）实际检查什么。

## 验收标准是什么

一条验收标准回答的是“必须满足什么”。它点出一个审阅者、一项测试或一个代理能够接受或拒绝的条件。它不点出要调用的函数，也不点出要改的文件。那些选择属于实现。

需求是规则。在 SpecScore 的 [需求规格](https://github.com/specscore/specscore/blob/main/spec/features/requirement/README.md) 里，需求是功能中的一个 `#### REQ:` 标题：一项义务，自带标识符。验收标准把相关需求收成一个条件。场景是一个具体案例，用来表明该条件成立。[验收标准规格](https://specscore.md/acceptance-criteria-specification) 是这样说的：标准回答“必须满足什么”，场景回答“如何证明”。

PRD 可以把同样的意图写进段落。段落帮得上人。代理仍然得猜测哪一句才是通过或失败的检验。把标准写成独立的一句，猜测就没有了。

## 是什么让验收标准可被验证

AI 编程代理能够照着一条标准做，前提是后来的读者能指出证据，并说出通过或失败。五种习惯让这件事成为可能。没有一种是句子模板。它们是可以套到任何模板上的检查，包括 Given/When/Then 和一份清单。

### 可观察

结果是局外人能看见的东西：页面上的一条消息、一条存下来或没存下来的记录、一次重定向、一份被拒绝的表单。“系统理解购物者”不可观察。“页面在邮箱字段旁边显示 Enter a valid email address”可以观察。

### 具体

点出行为者、输入和结果。“坏输入会被处理”没有说是哪一种输入、哪一种处理。“购物者提交邮箱 not-an-email”说了。具体并不是把每一个生产环境的值都贴上来。它是说，读者可以自己准备一份测试数据，并且知道应该发生什么。

### 一个行为

一条标准，一个行为。“购物者可以登录、重置密码，并看到锁定消息”是一句话里的三条标准。代理实现了第一条、跳过第三条，仍然可以说这句话做完了。把它们拆开。

### 通过或失败

可验证的验收标准有两种结局。“表单应该感觉很快”是程度，不是一个结果。如果需要一条界限，就把界限写出来：“结账页显示错误，并且不再向服务器发出进一步的请求。”如果说不出失败长什么样，这句话还不是一条标准。

### 去掉含糊的词

去掉把结果藏起来的词：优雅、直观、健壮、用户友好、无缝、快、应该、适当。把每一个换成你愿意接受的证据。“优雅地处理错误”变成“在字段旁边显示 Enter a valid email address，并且不要下单”。

## Given/When/Then 和一份清单

团队真正留得住的验收标准示例，大多落在两种形态里。Given/When/Then 是一个场景。清单是一串结果。状态、动作、结果的顺序要紧时，用场景。要列出同一个动作各自独立的结果时，用清单。

Cucumber 的 [Gherkin 参考](https://cucumber.io/docs/gherkin/reference/) 给步骤配上关键字 Given、When、Then、And 和 But。这份参考用英文；Gherkin 也有这些关键字的译文。关键字后面的文字，会在 Cucumber 运行文件时对上称为步骤定义（step definition）的代码。SpecScore 不用那一层。下面的例子是 Gherkin，好让这种形态看起来眼熟。它不是 SpecScore 文件。

### Given/When/Then

```gherkin
Scenario: Reject an email that is not a valid address
  Given the shopper is on the checkout form
  When the shopper submits the email "not-an-email"
  Then the page shows "Enter a valid email address" beside the email field
  And the order is not placed
```

### 清单

```markdown
- Submitting the email "not-an-email" shows "Enter a valid email address" beside the email field.
- The order is not placed.
```

When 只留一个。第二个动作就是第二个场景。And 延续同一个关键字：另一个 Given，或另一个 Then。不要用 And 夹带第二个行为。

清单是同一份约定，只是没有关键字。每一行仍然是一个可观察的结果。上面的清单就是场景的 Then，写成一项项检查。

## 这和 EARS 需求的关系

这些句式写在[EARS 需求语法详解与示例](https://specscore.md/zh-cn/articles/ears-requirements/)。

[Alistair Mavin 的指南](https://alistairmavin.com/ears/) 把 EARS（Easy Approach to Requirements Syntax）定义为一种温和地约束文字需求的机制。子句保持同一个顺序。指南给出的通用形态是：While <optional pre-condition>, when <optional trigger>, the <system name> shall <system response>。一条需求有零个或多个前置条件、零个或一个触发、一个系统名，以及一个或多个系统响应。

那句话是义务：系统应当满足的规则。验收标准是这项义务要算作已满足时必须为真的条件。一个 Given/When/Then 场景是这个条件的一个具体案例。需求可以写成 EARS 句子，标准仍然可以不含示例输入。

在 SpecScore 的功能里，需求可以是 `#### REQ:` 标题下的一句 EARS 风格的话。标准保持为抽象条件。场景带着示例。EARS 并不取代这两者。它只约束需求那一句。

## 验收标准示例：含糊的和可验证的

### 点出证据

**含糊**

```text
The checkout form should handle bad input gracefully and feel fast.
```

**可验证**

```text
When the shopper submits the email "not-an-email", the checkout page shows "Enter a valid email address" beside that field and does not place the order.
```

含糊的那一行藏着两项要求（错误和速度），也没有证据。可验证的那一行有一个动作，以及人或代理都能核对的两个结果。示例保持英文，因为格式关键字是英文。

### 一个行为

**含糊**

```text
Users can sign in, reset a password, and see an error if the account is locked.
```

**可验证**

```text
When a visitor submits a known email and the matching password, the app sets a session cookie and redirects to /dashboard.
```

登录、重置和锁定是三个行为。可验证的那一行只留下登录。重置和锁定各自得到自己的标准，带着自己的输入和结果。

### 写结果，不写函数名

**含糊**

```text
The handler returns 400 from validateEmail().
```

**可验证**

```text
When the shopper submits an email that is not a valid address, the checkout page shows "Enter a valid email address" and does not place the order.
```

函数名和状态码把标准绑在今天的代码上。检查挪到另一个函数，结果仍然成立。当状态码本身就是你承诺的行为时，把它留下来，并且写成可观察的结果。

## SpecScore 如何保存它们，以及 CI 检查什么

SpecScore 在 [specscore/specscore](https://github.com/specscore/specscore) 仓库发布格式，在 [specscore/specscore-cli](https://github.com/specscore/specscore-cli) 发布 CLI。两份文档描述验收标准时，形态并不完全一样。两份都是现行的。这一节先说明哪一份是哪一种，然后说明 `specscore spec lint` 实际拒绝什么。

这份[验收标准规格](https://specscore.md/acceptance-criteria-specification)，也就是该 URL 上提供的功能 README，把标准写在功能 README 里面。每个功能都有一节 `## Acceptance Criteria`。每条标准是一个 `### AC: {slug}` 标题。slug 使用小写，并用连字符连接。下一行是 `**Requirements:**`，按下面的形式列出它所收拢的需求：`{feature-id}#req:{slug}`。后面是散文，也就是合成后的条件。规格说，这段散文保持抽象。它不得包含 Given/When/Then 步骤、示例输入或实现细节。还没有标准时，这一节读作 “Not defined yet.”，Open Questions 一节写 “Acceptance criteria not yet defined for this feature.”。标准是可选的分组：规格说，收拢两条或更多需求时加一条，若只会复述单条需求就省掉。

具体的证明是功能的 `_tests/` 目录下的场景文件。[场景规格](https://github.com/specscore/specscore/blob/main/spec/features/scenario/README.md) 要求有一个 `# Scenario:` 标题、一节 `## Steps`、大写的 `GIVEN`、`WHEN`、`THEN` 和 `AND`，并且恰好一个 WHEN。可选的 `**Validates:**` 行把场景连到 `{feature-path}#ac:{slug}`，或连到一条需求。`## Rehearse` 一节可以在 `rehearse` 围栏里放一段可执行脚本，脚本以 shebang 开头。

CLI 也读取精简的标准文件，每条标准一个，位于 `_acs/<slug>.ac.md`。[登录示例](https://specscore.md/example-login) 和 [Rehearse 指南](https://specscore.md/rehearse) 记录了这种形态：`# AC: <slug>`，可选的 `**Status:**`、`**Verifies:**` 和 `**Applies-to:**`（这三项都可选），以及 `## Statement` 下面的条件。`specscore rehearse acs <feature-dir>` 根据这些文件打印一张摘要表。`specscore rehearse acs --write` 用这张表，外加一条说明该节由生成而来、不要手改的注释，替换功能 README 里的 Acceptance Criteria 一节。没有 `# AC:` 标题的文件，会被该命令报为错误。

本仓库的 CI 是 [dogfood 工作流](https://github.com/specscore/specscore/blob/main/.github/workflows/dogfood.yml)。它安装 CLI v0.39.1，并在拉取请求上、以及向 main 的推送上运行 `specscore spec lint`。2026年11月3日核对过，该发行版和 CLI 的默认分支都包含下面这些规则。检查器查的是结构。它不给英文打分。

规则 `ac-heading-format` 是一条错误。在功能 README（标题为 `# Feature:` 的文件）里，看起来像验收标准标题的一行必须恰好是 `### AC: <id>`，冒号后一个空格，id 为 kebab-case。形如 `### AC: <id> (verifies ...)` 的标题也被接受。`--fix` 只修复空白。带大写字母或下划线的 id 会被报告，并留给人处理。这条规则不要求有 `**Requirements:**` 行，不要求 “Not defined yet.” 占位，散文里出现 Given、When 或 Then 时也不警告。规格里为那条警告所写的场景，并不是这支 CLI 里的 lint 规则。

当一份计划点名了来源功能时，检查器会读取该功能 Acceptance Criteria 一节里的 `### AC:` 标题。规则 `P-002` 报告任务的 `**Verifies:**` 值不是这些标题之一所对应的 `<feature-slug>#ac:<ac-slug>`。规则 `P-001` 报告没有任何任务覆盖、并且没有列在 `## Deferred AC Coverage` 下面的标题。这两条规则不打开 `_acs/*.ac.md`。`rehearse acs --write` 产生的摘要表没有 `### AC:` 标题，所以计划规则看不到那些精简文件。

同一条 lint 命令不检查场景步骤的关键字。`specscore rehearse` 运行场景文件：Rehearse 指南在围栏命令上方使用 `## Given` 或 `## When` 这样的标题，并用 `**Use:**` 调用可复用的检查。一个文件里有一个 Given 和若干 When 分支时，会作为各自独立的案例运行。`specscore rehearse new <feature-slug>#ac:<ac-slug>` 读取功能 README，并把该标准里的 Given/When/Then 行抽到 `spec/features/<feature-slug>/_tests/<ac-slug>.md`。这些都不在 dogfood 工作流里运行。工作流运行的是 `specscore spec lint`。

### 内联标准，来自验收标准规格

```markdown
## Acceptance Criteria

### AC: invalid-email-rejected

**Requirements:** checkout#req:reject-invalid-email

An email that is not a valid address is rejected, and the order is not placed.
```

### 精简标准文件，rehearse acs 读取的形态

```markdown
# AC: invalid-email-rejected

**Status:** accepted
**Verifies:** checkout#req:reject-invalid-email

## Statement

An email that is not a valid address is rejected, and the order is not placed.
```

### 场景文件，来自场景规格

```markdown
# Scenario: Reject an invalid email

**Validates:** [checkout#ac:invalid-email-rejected](../README.md#ac-invalid-email-rejected)

## Steps

GIVEN the shopper is on the checkout form
WHEN the shopper submits the email "not-an-email"
THEN the page shows "Enter a valid email address" beside the email field
AND the order is not placed
```

### Rehearse 场景，Rehearse 指南里的形态

~~~~markdown
# Rehearse: Reject an invalid email

## Given a submitted email that is not a valid address
```bash
printf 'email=not-an-email\n' > form.txt
```

## When the response is recorded
```bash
printf 'Enter a valid email address\n' > out.txt
```

## Then the message is the invalid-email text
```bash
grep -q 'Enter a valid email address' out.txt
```
~~~~

这四份样品用来说明那些形态。它们不是一棵完整的功能树，本页也不声称它们单独就能通过 `specscore spec lint`。功能 README 还需要其他必填小节，那条命令才会干净。

如果想要一份问题清单，用来从含糊的请求里把标准问出来，用 [提出好问题](https://specscore.md/asking-good-questions)。登录演练把一条精简标准 `session-hardened` 放在其他产物之中。

## 常见问题

### 什么是验收标准？

验收标准（AC）是一项变更要算作完成之前必须为真的条件。在 SpecScore 的验收标准规格里，它们是抽象的：说明必须满足什么，把具体步骤留给场景。

### 什么是 Given/When/Then？

Given/When/Then 是 Gherkin 里的步骤形态：Given 是起始状态，When 是发生一个动作，Then 是随后出现的可观察结果。Cucumber 在 Gherkin 参考里记录这些关键字。SpecScore 的场景规格使用大写关键字 GIVEN、WHEN、THEN 和 AND，每个场景文件恰好一个 WHEN。

### 怎样为 AI 编程代理写验收标准？

写一个可观察的行为，带上行为者、输入和结果，并且不要含糊的词。示例数据放进场景，不要放进抽象标准。代理就可以对着标准实现，并核对场景。

### 验收标准和测试用例是一回事吗？

不是。标准陈述条件。测试用例，或 SpecScore 场景，是带具体输入的一次运行，用来表明条件成立或失败。多个场景可以支撑同一条标准。

### specscore spec lint 会评判含糊的措辞吗？

不会。在 CLI v0.39.1，也就是本仓库 CI 所安装的发行版里，ac-heading-format 检查 ### AC: 标题。它不为 “graceful” 或 “fast” 这类词打分，标准里出现 Given/When/Then 时也不警告。措辞是写作的事。[验证器页面](https://specscore.md/zh-cn/validators/) 把那种确定性的 lint 和人工的 AI 审阅分开。

### Given/When/Then 步骤在 SpecScore 里放在哪里？

放在场景里，不放在验收标准里。验收标准规格禁止在标准内部写 Given/When/Then 步骤。场景规格把它们放在 _tests/ 下的 ## Steps 里。Rehearse 运行一种相关形态：_tests/ 下的标题和围栏命令，记录在 Rehearse 指南里。检查器不强制这些步骤关键字。

## 来源

阅读于 2026年11月3日。第三方页面只按其发布的定义引用。

- [Cucumber — Gherkin 参考](https://cucumber.io/docs/gherkin/reference/)
- [Cucumber — 行为驱动开发](https://cucumber.io/docs/bdd/)
- [Alistair Mavin — EARS](https://alistairmavin.com/ears/)
- [EARS 需求语法详解与示例](https://specscore.md/zh-cn/articles/ears-requirements/)
- [SpecScore — 验收标准规格](https://specscore.md/acceptance-criteria-specification)
- [specscore/specscore — 验收标准功能](https://github.com/specscore/specscore/blob/main/spec/features/acceptance-criteria/README.md)
- [specscore/specscore — 场景功能](https://github.com/specscore/specscore/blob/main/spec/features/scenario/README.md)
- [specscore/specscore — 需求功能](https://github.com/specscore/specscore/blob/main/spec/features/requirement/README.md)
- [SpecScore — 功能规格](https://specscore.md/feature-specification)
- [SpecScore — Rehearse](https://specscore.md/rehearse)
- [SpecScore — 登录示例](https://specscore.md/example-login)
- [SpecScore — 提出好问题](https://specscore.md/asking-good-questions)
- [specscore/specscore-cli](https://github.com/specscore/specscore-cli)
- [specscore/specscore](https://github.com/specscore/specscore)
- [dogfood 工作流（CLI v0.39.1）](https://github.com/specscore/specscore/blob/main/.github/workflows/dogfood.yml)
- [specscore-cli — ac-heading-format](https://github.com/specscore/specscore-cli/blob/main/pkg/lint/ac_heading.go)
- [specscore-cli — 计划规则 P-001 和 P-002](https://github.com/specscore/specscore-cli/blob/main/pkg/lint/plan_rules.go)

## 相关

- [面向 AI 编程的 Markdown PRD 模板](https://specscore.md/zh-cn/articles/feature-spec-template/)
- [在 CI 中用规格检查器检查规格](https://specscore.md/zh-cn/articles/lint-specs-in-ci/)
- [EARS 需求语法详解与示例](https://specscore.md/zh-cn/articles/ears-requirements/)
- [规范驱动开发](https://specscore.md/zh-cn/spec-driven-development/)
- [规范驱动开发工具](https://specscore.md/zh-cn/spec-driven-development/tools/)
- [验收标准规格](https://specscore.md/acceptance-criteria-specification)
- [提出好问题](https://specscore.md/asking-good-questions)
- [以示例看一个登录功能](https://specscore.md/example-login)
- [Rehearse](https://specscore.md/rehearse)
- [SpecScore 验证什么](https://specscore.md/zh-cn/validators/)
- [CLI](https://specscore.md/zh-cn/cli/)
