API 测试各种边界情况
边界情况总是被忽略
大多数 API 测试只覆盖「正常流程」。
用户输入正确的数据。服务器返回预期的响应。一切运行良好。
但现实世界不是这样的。用户会发送空字符串。网络会突然断开。数据库会返回异常数量的记录。
边界情况是系统的真实测试。它们暴露设计的缺陷。它们揭示假设的脆弱。
💡 Click the maximize icon to view in fullscreen
为什么边界情况重要
边界情况不是异常。它们是常态的一部分。
一个空数组不是错误。它是有效的数据结构。一个超长的字符串不是攻击。它可能只是用户的真实输入。
系统需要处理这些情况。不是因为它们罕见,而是因为它们不可避免。
当你跳过边界情况测试,你不是在节省时间。你是在积累债务。这个债务总会在生产环境中暴露。
边界情况的类型
边界情况不是无限的。它们遵循模式。
空值情况:
null、undefined、空字符串""- 空数组
[]、空对象{} - 空白字符串
" "
极端值:
- 最小值:
0、-Infinity、负数 - 最大值:
Number.MAX_SAFE_INTEGER、超长字符串 - 特殊值:
NaN、Infinity、科学计数法
格式边界:
- Unicode 字符、Emoji
- SQL 注入字符:
'; DROP TABLE -- - XSS 向量:
<script>alert(1)</script> - 路径遍历:
../../etc/passwd
分页和限制:
- 零结果
- 恰好一条记录
- 刚好达到限制(如 100 条)
- 超出限制(如 1000 条)
批量操作:
- 空批次
- 单个项目的批次
- 最大允许批次大小
- 超出批次大小限制
时间相关:
- 过去的时间戳
- 未来的时间戳
- Unix epoch 零点:
1970-01-01 - 时区边界情况
超时和延迟:
- 请求超时前一秒
- 请求恰好超时
- 极慢的响应(几分钟)
- 并发请求导致的竞态条件
资源状态:
- 资源不存在(404)
- 资源已被删除(410 Gone)
- 资源被锁定(423 Locked)
- 资源冲突(409 Conflict)
权限边界:
- 未认证用户
- 认证但未授权
- 过期的 token
- 被吊销的权限
测试边界情况的思维方式
边界情况测试不是清单。它是一种思维方式。
问自己:这个函数假设了什么?
如果它假设数组不为空,测试空数组。 如果它假设字符串是 ASCII,测试 Unicode。 如果它假设网络稳定,模拟超时。
每个假设都是一个潜在的边界情况。
💡 Click the maximize icon to view in fullscreen
实践中的边界测试
好的边界测试有几个特征。
它是独立的。 每个测试只验证一个边界情况。不要在一个测试中混合多个边界。
它是可重复的。 无论运行多少次,结果都相同。边界情况测试不应依赖外部状态。
它是有意义的。 测试真实可能发生的边界,而不是理论上的极端情况。
// ❌ 不好的测试:混合多个边界
test('handles edge cases', () => {
expect(api.create(null)).toThrow()
expect(api.create('')).toThrow()
expect(api.create([])).toThrow()
})
// ✅ 好的测试:每个边界独立
test('rejects null input', () => {
expect(() => api.create(null))
.toThrow('Input cannot be null')
})
test('rejects empty string', () => {
expect(() => api.create(''))
.toThrow('Input cannot be empty')
})
test('handles empty array gracefully', () => {
const result = api.batchCreate([])
expect(result).toEqual([])
})
边界情况揭示设计问题
边界情况测试不只是防御性编程。它是设计反馈。
当一个函数难以测试边界情况,通常说明设计有问题。
过多的 if-else 处理边界?可能需要重新思考函数职责。
边界情况需要复杂的 mock?可能耦合度太高。
边界测试代码比实现还长?可能逻辑过于复杂。
边界情况是系统的镜子。它反映设计的清晰度。
最后
边界情况测试不是为了达到 100% 覆盖率。
它是为了理解系统在什么情况下会失败。
理解失败比追求完美更重要。因为所有系统都会失败。唯一的区别是,你是否知道它会在哪里失败。
边界情况测试让失败变得可预测。可预测的失败可以被处理。可以被监控。可以被修复。
这就是边界情况测试的价值。
- 你的 API 在什么边界情况下会崩溃?
- 哪些边界情况是你从未考虑过的?
- 生产环境中最常见的边界情况失败是什么?
- 你的错误处理能区分边界情况和真正的错误吗?
参考资源
Related Posts
Articles you might also find interesting
端到端 Postback 模拟测试
真实的测试不是模拟完美的流程,而是重现真实世界的混乱。Postback 测试的价值在于发现系统在不确定性中的表现。
文档标准是成本计算的前提
API文档不只是写给开发者看的。它定义了系统的边界、成本结构和可维护性。统一的文档标准让隐性成本变得可见。
用 AI Agents 加速测试环境配置
测试环境的配置是重复的琐事。环境变量、测试数据库、配置文件——这些步骤消耗时间但不产生直接价值。AI agents 改变了这个等式。
在Claude Code中写单元测试:简单高效的实践
测试不是负担,是对话。Claude Code改变了测试的成本结构,让测试回归本质:验证行为,而非追求覆盖率。
CRUD 操作
四个字母背后,是数据的生命周期,是权限的边界,也是系统设计的基础逻辑
让文档跟着代码走
文档过时是熵增的必然。对抗衰败的方法不是更频繁的手工维护,而是让文档"活"起来——跟随代码自动更新。三种文档形态,三种生命周期。
错误隔离
失败是必然的。真正的问题不是失败本身,而是失败如何蔓延。错误隔离不是为了消除失败,而是为了控制失败的范围。
实现幂等性处理,忽略已处理的任务
在代码层面识别和忽略已处理的任务,不是简单的布尔检查,而是对时序、并发和状态的深刻理解
使用Jest或Vitest作为测试框架有什么区别?
测试框架的选择不是功能列表的比较,而是关于工具哲学的选择。Jest代表完整性,Vitest代表原生性。
缺少Jest依赖时的测试选择
测试不一定需要框架。有时候最简单的工具已经足够。Node.js内置的assert模块和基础测试能力,往往比庞大的测试框架更清晰。
幂等性检查
在不确定的系统中,幂等性检查是对重复的容忍,是对稳定性的追求,也是对失败的预期与接纳