编码前的边界情况检查清单

边界情况不提前写,开发阶段一定会靠默认假设补齐。

快速结论

至少检查空值、重复请求、数量上限、权限差异、并发冲突、外部依赖失败和回滚成本。把这些项写入规格后,很多线上事故会在评审时就消失。

你应该先看什么

可直接复用的示例

边界清单
- Null / Empty / Unknown
- Duplicate / Retry / Reorder
- Permission / Visibility
- State Transition / Concurrent Write
- Expiry / DST / Time Zone
- Timeout / Partial Failure / Rollback

怎样写出 QA 能直接执行的边界条件

一条边界条件可测试,需要三件事都具体:起始状态和输入、触发动作、可观察的预期结果。只要其中一项是没有宾语的动词,或者拿形容词充当数字,它就还不是测试用例。判断方法很简单:QA 不问任何人,能不能直接写出这个测试?"合理处理边界情况"这种句子第一关就过不去。

不管规格用 Given/When/Then 还是 fixture 列表,每条边界条件都写同样几栏:

边界条件:未付账单上连点两次 Pay Now
- 起始状态:用户有一张未付账单,已保存的银行卡已过期
- 输入:    60 秒内用同一个 invoice_id 重试支付
- 动作:    用户连续点击两次 Pay Now
- 预期:    只向支付服务发起一次扣款;第二次请求
            返回已有的 attempt_id
- 证据:    账本里只有一条 pending 支付记录;页面只显示一条横幅
- Fixture: duplicate-pay-now.json

给 fixture 起名字比看起来更重要。fixture 进入测试仓库后,API、UI、日志和数据库断言都能复用同一份输入,边界条件就不再只是文档里的一句话。

同样的写法,能把一句模糊的话改成 QA 不用开会就能执行的用例:

修改前:
- 网络失败时要优雅处理。

修改后:
- Given 已登录买家的购物车里有一个 49 美元商品
  And 支付服务已经扣款,但应用收到 30 秒 timeout
  When 买家在 2 分钟内再次点击 Pay
  Then 应用复用原来的 idempotency key
   And 页面显示"支付处理中",不会再次扣款
   And 客服能通过 order_id 在审计日志里查到这笔 pending 支付

两条结构规则让清单保持可执行。第一,写行为,不写实现:"后端重试三次"留着,"后端用指数退避加 Bloom filter 缓存"删掉。第二,每条边界条件都落到测试者能观察的结果上;"记一条 warning 日志"这类内部行为,只有规格明确把这条日志指定为观察点时才算数。

边界条件放在正常路径验收标准下面的独立小节,按本页的类别分组,格式和正常路径保持一致。具体写法见验收标准示例与写法。

输入边界:每个字段试六个值

每个输入最多有六个值得测的取值:空值、一个、允许的最大值、最大值加一、负数、Unicode 或特殊字符。不是每个字段都六个全适用,但在规格说明之前,默认它们都可能出问题。以上限 50 个字符的显示名为例:

- Given 显示名字段(最多 50 个字符)
  When 用户提交 ""(空值)
  Then 表单内联提示"名称不能为空",提交按钮禁用

- Given 显示名字段
  When 用户提交 51 个字符
  Then 请求被拒绝,返回 422 和"名称过长(最多 50 个字符)"

- Given 显示名字段
  When 用户提交包含 emoji(4 字节 Unicode)的 50 个字符
  Then 请求成功,名称在用户列表中正确显示

emoji 这一条最容易被漏掉。客户端、API 和数据库可能分别按码点、UTF-16 单元或字节计算长度,规格要写明上限按哪种计数。下面高风险领域示例里的文件大小用例,用的也是"恰好等于上限"和"上限加一"这一模式。

状态转换、并发写入与时间边界

错误状态下的转换

只要有 status 字段,就有可能走错的转换。规格通常只写正常路径(active → cancelled),跳过重新激活、重复取消、变更挂起期间取消这些情况。每个转换都要写清:在错误状态下尝试时会发生什么。"用户取消一个已经取消的账户"是真实场景,上线后很快就会出现。

并发写入

只要两个用户能操作同一条记录,功能就有并发规则,不管规格写没写。要测的是两人在 100ms 内同时提交时会发生什么。规格要回答三个问题:

规格没写明"后写覆盖并显示冲突横幅"是规则,QA 就没法测它。

时间相关的边界

时间边界在时钟跨过那个点之前都看不见:

每条正常路径至少三条错误路径

每条正常路径的验收标准,至少对应三条错误路径:校验失败、权限不足、下游依赖失败。规格要写出每种情况下用户看到什么,包括状态码和确切文案:

- Given 用户已登录但没有 "admin" 角色
  When 请求 /admin/users
  Then 返回 403,body 为 {"error": "forbidden", "required_role": "admin"}
  And 页面显示"你没有访问此页面的权限",并提供返回链接

- Given 下游计费服务返回 503
  When 用户提交结账
  Then 页面显示"支付暂时不可用,请一分钟后重试"
  And 服务端以指数退避最多重试 3 次
  And 至少一次重试成功之前不创建订单记录

失败要写成场景,不要只写名词。"上传中断"没法测;"用户在慢网络下上传 200 MB 文件,进度到 80% 时断网,随后刷新浏览器"就可以,而且它逼着产品做选择:断点续传、明确失败并允许重试,还是明确丢失进度。三种都可测,前提是规格选定其中一种。

高风险领域示例

以下示例涵盖三个边界情况代价最高的领域:支付处理、身份认证和文件摄入。在这些场景中,晚发现边界情况的修复成本极高。

# 支付处理边界情况
- amount = 0.00 → 校验错误拒绝,不调用支付网关
- 购物车与银行卡币种不匹配 → 400,不扣款
- 扣款发起后网关超时 → 记录模糊状态,触发对账任务,返回 503 含 retryable 标记
- 10 秒内重复支付请求 → 返回原始收据,不二次扣款

# 身份认证边界情况
- token 过期后 1ms 使用 → 401,非 403
- 有效 token 但账号已停用 → 403 含 account_suspended 错误码
- 同时从 3 个会话登录 → 最早会话失效,新会话签发,审计日志记录全部三个事件
- 重置密码链接首次点击后再次使用 → 410 Gone

# 文件上传边界情况
- 文件大小恰好达到上限(10MB)→ 接受
- 文件大小超过上限 1 字节 → 413 含明确大小提示
- 文件类型伪造(PDF 扩展名但内容为可执行文件)→ 内容检测拒绝,非扩展名检测
- 上传中途中断 → 丢弃不完整文件,不创建记录

支付边界情况到达生产通常导致双重扣款或未对账交易——两者修复成本均高。认证边界情况未文档化会产生不一致的安全行为,在审计前不可见。文件边界情况是内容检测缺失时最常见的安全漏洞来源。

优先级排序:风险 × 可发现性

并非每个边界情况都需要相同程度的规格细节。实用的优先级排序使用两个维度:出错后的后果(风险),以及 QA 或监控在生产中发现它的难易程度(可发现性)。高风险叠加低可发现性,要求在实现前明确写入规格并映射测试用例。

这个优先级排序的输出不是所有可能边界情况的完整列表——那既不可能也适得其反。而是团队在编码前达成共识的优先列表,每项均标明测试责任人和解决时限。

哪些边界条件会阻断发布

风险 × 可发现性决定一条边界条件要在规格里写多细。还有一个决定也应该写进规格:它的测试失败时怎么办。提前写好规则,QA 就不用在执行测试时临场谈优先级。

边界条件优先级规则

阻断发布:
- 资金流转可能重复或丢失
- 权限边界可能被绕过
- 用户数据可能在没有提示的情况下被覆盖
- 重试可能产生第二次不可逆操作
- 客服无法识别失败状态

可作为已记录风险上线:
- 文案与界面不一致的外观问题
- 罕见且不丢数据的时序问题
- 非关键通知延迟,且有明确重试路径
- 仅管理员使用、有人工兜底方案的流程

需要中间档的团队,可以把其余边界分成 release-blocking、should-test 和 exploratory 三级。叫法不重要,重要的是测试一失败,团队马上知道该停止发布、接受风险,还是放进下个迭代。

跨系统层的边界情况

边界情况在不同系统层的分布规律各异。一个用户操作可能同时在 API 层、服务层、数据库层和后台任务层暴露各自的边界情况。规格应标明每个边界情况所在的层次,以确保正确的工程师负责对应的测试。

任务层的边界情况是最常见的覆盖盲区。由于任务异步运行,其失败对用户在请求时不可见——这正是它们需要明确规格覆盖的原因。一个静默丢弃记录的未文档化任务失败模式,比用户可以重试的同步错误危害更大。

真实评审矩阵:大报表导出

边界清单要落到具体功能里才有用。下面是一个大报表导出的评审矩阵,它把“用户点导出”拆成 UI、API、后台任务和存储四层。

功能:订单报表 CSV 导出

UI 层:
- 用户连续点击两次导出
- 期望:返回同一个 job_id,或第二次点击被禁用
- 证据:重复点击测试、按钮 loading 状态截图

API 层:
- 请求超过最大行数
- 期望:返回 413 或清晰校验错误,不创建 job
- 证据:row_limit fixture、错误文案检查

Job 层:
- worker 在生成一半时退出
- 期望:重试或标记 failed,并清理半成品文件
- 证据:worker failure test、partial file cleanup log

Storage 层:
- 下载链接过期
- 期望:显示过期状态,并允许有权限用户重新生成
- 证据:expired URL test、权限检查截图

这类矩阵比一长串泛泛的边界词更有用,因为它直接告诉评审者:哪一层负责处理,失败后用户看到什么,合并前要拿什么证据。

落地建议

拿最近一个上线后出问题的功能做回溯:这份清单里哪几项当时没覆盖?大多数时候答案是空值处理、重试幂等或并发写冲突。找到了就在下一个功能的规格里提前写进去。

如果团队觉得边界情况太多,可以先只记录“高风险且低可发现性”的项。这样不会让规格膨胀,同时能优先覆盖最可能造成生产损失的路径。

评审时也可以给每个边界情况标记处理方式:立即实现、写成非目标、加监控观察,或放入后续版本。标记后的清单更容易进入测试计划和发布检查,也能避免团队把所有边界情况都当成同等优先级。

更新日期:2026-11-06

相关文章

评审时如何使用这份清单

建议在实现前把清单拆给不同角色:产品检查空状态和用户期望,研发检查并发、幂等和数据边界,QA 检查每个边界是否有可执行用例,运维或值班负责人检查异常是否可观测、可告警、可回滚。

如果某个边界暂时不处理,也要写清理由和风险接受人。未处理的边界不一定阻止上线,但它应该是一个明确决策,而不是评审时漏看的空白。

QA 什么时候应该把规格退回

看到下面这些情况,QA 完全有理由把规格退回去修改:

规格定稿前跑一遍"五分钟测试":挑一条边界条件,假装自己是 QA 来写测试。不打开代码、不问人,就能写出确切的输入、触发和断言,这条就过了;写不出来,规格还欠一句话。仍需产品拍板的边界条件,退回规格里写成开放问题,别让 QA 在执行测试时才发现这个决定没人做。

把边界条件接回测试和发布

边界条件只有进入测试和发布判断,才算真正完成。评审结束后,应把高风险项分别落到自动化测试、手工验证、监控指标或发布观察清单里。无法验证的边界条件,需要改写成可观察状态,例如错误码、空状态文案、审计日志或告警阈值。

如果某个边界条件暂时不处理,也要写进非目标或后续任务。这样上线后出现相关问题时,团队能判断这是有意延期,还是评审时遗漏。

编辑说明

本指南涵盖 编码前的边界情况检查清单,面向 Spec-First 工程团队。示例为说明性场景,非生产代码。