如何撰写可测试的软件规格文档

如何撰写可测试的软件规格文档
Daniel Marsh · Spec-First 工程笔记

"可测试"的规格并不是用某种特殊语言写成的规格。它是一份每一条声明都能由未参与撰写的人来核验的规格。这个门槛听起来不高,其实不然。我审阅过的大多数规格在第一轮就过不了这关——包括我自己写的。

发布于 2025-12-28 · ✓ 已更新 2026-05-06 · 阅读约 8 分钟 · 作者:Daniel Marsh · 审校:编辑政策

现场笔记:最快识别不可测试规格的方法

我会直接问一个很笨的问题:QA 要准备什么 fixture?如果没人说得出初始状态、触发动作和预期输出,那句话还不是可测试需求。

不可测试:
- 仪表盘能优雅处理过期数据。

可测试:
- Given 库存数据超过 15 分钟未同步
  When 仪表盘加载
  Then 时间戳旁显示 stale 标记
  And 同步任务完成前禁用刷新

可测试性到底需要什么

当一条声明能被某人执行一次具体的检查,并得到确定的"是"或"否"时,它才算可测试。这要求四件事情被钉死:初始状态、输入或触发条件、可观察的结果,以及检查本身。少一项,声明就塌陷成了个人观点。

对比下面两句话,都是我实际审阅过的规格里摘出来的:

第一句不可测试。第二句里有四条可测试的声明:前置条件(索引命中)、触发条件(提交)、可观察项(10 条结果页、p95 延迟、相关性排序)、检查方式(计数、测量、核验顺序)。永远写第二种。

四个要素

明确的初始状态

"Given 用户已登录"太弱——以什么角色登录?具备什么权限?测试数据库里有没有数据,还是空的?

强的前置条件会把相关状态一条条列出来:

Given a user account with role "admin" in workspace "acme",
  AND the workspace has 3 projects,
  AND project 2 has status "archived"

是的,字数更多了。但这些本来就是 QA 会在 Slack 上问你的问题。在规格里写一次就好。

输入,而非意图

"用户搜索最近的订单"描述的是意图。"用户向 /orders?date_range=last_7_days&status=open&limit=20 发送一个 GET 请求"描述的是输入。

如果 API 形状还没定下来,规格不必规定具体的 URL。但它必须对用户能问什么做出承诺。"最近的订单"不是输入——它是一个概念,而实现方会自行挑一个解释。

带阈值的可观察结果

大多数规格就是在这一步变得不可测试的。"可接受的延迟"、"合理的默认值"、"优雅降级"——这些都不是结果,它们是感觉。

把每一个形容词都换成数字或枚举:

检查本身

如果结果是可观察的,QA 就需要知道怎么去观察它。对 UI 功能,指名元素或文案。对 API 功能,指名状态码和响应结构。对异步功能,指名最终状态和最大等待时间。

"展示错误消息"还不够。"提交后 2 秒内,表单上方出现一条错误 banner,文案为'Card declined — please use a different payment method'"才够。

非功能性需求,以可测试的方式

那些常见的非功能性需求——性能、安全、可靠性、可访问性——正是规格最爱含糊的地方。它们本不必如此。

重写测试法

拿起规格里的任何一条声明,试着把它转成一个测试函数签名。转不出来,就还不可测试。

// Untestable claim:
// "The system handles errors gracefully"
// Test signature: ???  (no input, no expected output)

// Testable claim:
// "On backend 503, the UI shows the retry banner within 2s and enables
//  a 'Try again' button that resubmits the last request"
test('shows retry banner on 503 within 2s', async () => {
  mockBackendResponse(503);
  await submitForm();
  expect(screen.queryByText('Service unavailable')).toBeVisible({ timeout: 2000 });
  expect(screen.queryByRole('button', { name: 'Try again' })).toBeEnabled();
});

我审稿时看什么

审阅规格的可测试性时,我会在心里把每个形容词和每个被动结构都高亮出来。这两者通常都是规格在做一项自己无法核验的声明的地方。有时形容词没问题,因为前面已经给出了可衡量的定义。更多时候并没有。

快速清单:

真正能感受到的收益

可测试的规格不只帮 QA。它缩短实现周期,因为工程师不再打断产品去问"合理"到底是什么意思。它缩短评审周期,因为评审者有了具体的东西可以反驳。它还减少上线后的意外,因为团队在发布前就已经为那些数字吵过一架,而不是发布后。

代价是多花一个小时写。省下的是三周后那场四小时的会议——大家坐在那里,试图搞清楚这个功能原本到底是要做什么。

评审时看什么

这篇文章适合用在把规格改到 QA 可以直接写测试时。别从“原则”聊起,直接拿一条真实改动来对照,看看规格里还缺什么。

可测试规格的目标很朴素:QA 不需要追着作者问“这句话到底怎么看算通过”。

例子:不要写“导出要足够快”,改成“拥有 5 万行数据的用户在报表页导出 CSV,20 秒内拿到可下载文件;生成失败时看到可重试错误”。这句话同时给工程目标、QA 用例和客服解释口径。

落地例子

以密码重置为例,弱规格会写“邮件要及时发送,链接要安全”。可测试规格会写:token 30 分钟过期,只能使用一次;账号不存在时返回中性提示;每次请求写入安全事件但不暴露邮箱是否存在;连续请求触发限流。QA、安全和工程都能根据这些句子直接工作。

这类细节不是额外文档,而是产品行为本身。规格如果不写,代码也会有答案,只是答案由实现者临场决定。

再做一次反向检查:把规格里的每个形容词圈出来,比如“快速”“稳定”“安全”“友好”。如果这个词不能变成一个测试步骤、一个阈值或一个失败提示,就把它改写。测试性不是文风问题,而是交付团队能否在同一条线上验收。

最后把这些检查贴到 PR 描述里。评审者看到证据链接,就能判断规格是否真的被实现。

可测试规格一定有可观察输出

“系统正确处理”不可测试。可测试的写法会说清楚 API 响应、数据库状态、事件、日志或 UI 文案。至少要有一个外部可观察证据,否则 QA 只能问开发。

Untestable:
- System handles expired invite links.

Testable:
- Given an invite expired 24 hours ago
  When the user opens the link
  Then the API returns 410 with code invite_expired
  And no membership row is created
  And the UI shows "Ask the admin for a new invite"

边界:不是所有内部实现都要暴露给测试。测试看行为和证据,不看私有函数名。

测试矩阵可以很小

一份小 spec 只要三列:场景、预期证据、测试 owner。比如 API 返回、数据库行、事件、UI 状态各写一条。矩阵不是替代测试代码,而是让大家在实现前同意“什么证据算通过”。

对每条标准都问一句:失败时谁能看见?用户、API 调用方、日志、数据库表、监控还是客服后台。看不见的行为很难测试,也很难运营。规格要把可见性写出来。

规格里还可以加一列“测试位置”:unit、API contract、integration、E2E、manual QA。不同证据放在不同层级,owner 才清楚谁负责补测试代码、谁负责准备数据、谁负责验收 UI 状态。

如果某条标准找不到测试位置,就说明它还不是可测试规格。把它改成字段、状态、API 响应、数据库记录、事件或日志证据,再决定由哪个 owner 负责验证。这样规格会短一点,但更硬。

最后再补一个失败样本。没有失败样本的规格,常常只证明 happy path。失败样本能逼出 error code、空状态、权限状态和恢复路径。

如果还是测不了,就把这条从验收标准里拿出来,改成开放问题交给 owner 决定。可测试性不是文案润色,而是实现前的风险控制。

先问清楚,再写代码和用例,不要靠测试阶段补洞,也不要拖到上线前。

可复制产物:规格写作片段

当工单看似清楚但还缺验收语言时使用。它要求作者写出角色、触发、结果和证据。

规格写作评审片段:如何撰写可测试的软件规格文档

本次要做的决策:
- 把模糊需求改写成可以由工程和 QA 共同判断的验收条款。

责任人检查:
- 产品责任人:
- 工程责任人:
- QA 或运维评审:

范围边界:
- 本次包含:
- 本次不包含:
- 仍需确认的假设:

验收证据:
- 测试或 fixture:
- 日志、指标或截图:
- 人工复核步骤:

写作边界:避免模糊动词,每条验收标准都要有可见的通过或失败信号。

评审追问:
- 没参加需求会的人还会误解哪里?
- 哪个证据能证明这次改动足够安全,可以发布?

二次审阅记录:可测试不等于更长

这次复看是为了避免文章听起来像是在要求更多文档。可测试规格通常更短,只是把状态和证据写得更准。

改写规则:
- 用可观测结果替换形容词。
- 用状态变化替换“能工作”。
- 只有阈值会改变设计时,才写“多快”。
- 用具体 UI、API 或日志行为替换“优雅处理”。

编辑复核记录

复核日期:2026-04-28。本次补充了可复用产物,按相关主题 Hub 检查了文章定位,并收紧下一步链接,让页面更像可操作参考,而不是孤立长文。

关键词:可测试规格 · 验收标准 · 非功能性需求 · QA 可测试性

专题阅读路径

这篇文章归入 验收标准 主题。先读 Hub,再结合下面的清单、模板或工具落到具体项目里。

交互式生成规格
填写表单,生成完整的功能规格 Markdown——免费使用,无需注册。
试用规格生成器

编辑说明

最近复核:2026-04-28。编辑部检查了示例、内链和可复制评审片段,确保内容更适合真实项目使用。

本文面向软件交付团队,介绍如何撰写可测试的软件规格文档。示例均为工程场景说明,不构成法律、税务或投资建议。