测试策略
前言
你的代码真的"没问题"吗? 每次改完代码手动点一遍看看有没有坏——这种方式在项目小的时候还能凑合,但当代码量增长到几万行、团队扩展到十几人时,"手动点点看"就是一场灾难。
本章带你理解软件测试的核心策略,从测试金字塔到 TDD,建立系统化的质量保障思维。
这篇文章会带你学什么?
| 章节 | 内容 | 核心概念 |
|---|---|---|
| 第 1 章 | 测试金字塔 | 测试的层次与比例 |
| 第 2 章 | 单元测试实战 | 如何写好一个测试 |
| 第 3 章 | TDD 驱动开发 | 红绿重构循环 |
| 第 4 章 | 测试策略选择 | 不同场景的方案 |
学完本章,你将理解如何为项目选择合适的测试策略,写出有价值的测试,并通过 TDD 提升代码设计质量。
0. 全景图:为什么需要自动化测试?
想象你是一个建筑工程师。每次修改图纸后,你不会亲自爬上每一层楼去检查结构是否安全——你会依赖一套自动化的检测系统。软件测试就是代码世界的"结构检测系统"。
自动化测试的价值
- 回归保护:修改 A 功能时,自动检测 B、C、D 功能是否被影响
- 重构信心:有测试覆盖的代码,重构时心里有底
- 活文档:好的测试就是最好的使用说明书
- 快速反馈:几秒钟内知道代码是否正确,而不是等到部署后才发现问题
1. 测试金字塔:测试的层次与比例
1.1 三层金字塔
Mike Cohn 提出的测试金字塔是测试策略的经典模型。它告诉我们:不同类型的测试应该有不同的数量比例。
通过下面的交互组件,点击金字塔的每一层,了解各层测试的特点:
1.2 为什么是金字塔形?
金字塔形状反映了一个核心权衡:速度与真实度的取舍。
- 底层(单元测试):速度极快、数量最多、成本最低,但只能验证单个零件
- 中层(集成测试):速度适中、数量适中,验证零件之间的配合
- 顶层(E2E 测试):最接近真实用户,但速度慢、维护成本高、容易因环境问题失败
反模式:冰淇淋甜筒 —— 如果你的项目 E2E 测试最多、单元测试最少,那就是倒过来的"冰淇淋甜筒"。这意味着测试套件运行缓慢、经常失败、维护成本极高。
2. 单元测试实战
2.1 什么是好的单元测试?
好的单元测试遵循 FIRST 原则:
| 原则 | 含义 | 说明 |
|---|---|---|
| Fast | 快速 | 毫秒级完成,开发者愿意频繁运行 |
| Independent | 独立 | 测试之间互不依赖,可以单独运行 |
| Repeatable | 可重复 | 任何环境下运行结果一致 |
| Self-validating | 自验证 | 结果是明确的通过/失败,不需要人工判断 |
| Timely | 及时 | 在写代码的同时(或之前)写测试 |
2.2 测试的结构:AAA 模式
每个测试都应该有清晰的三段式结构:
test('应该正确计算含税价格', () => {
// Arrange(准备)—— 设置测试数据
const price = 100
const taxRate = 0.13
// Act(执行)—— 调用被测函数
const result = calculateTotalWithTax(price, taxRate)
// Assert(断言)—— 验证结果
expect(result).toBe(113)
})2.3 测试什么?不测什么?
应该测试的:
- 核心业务逻辑(价格计算、权限判断、数据转换)
- 边界条件(空值、零、负数、超大数)
- 错误处理路径
不需要测试的:
- 第三方库的内部实现
- 简单的 getter/setter
- 框架自身的功能(如 Vue 的响应式系统)
3. TDD:测试驱动开发
3.1 红绿重构循环
TDD(Test-Driven Development)的核心是一个简单的循环:先写测试,再写实现,最后重构。
通过下面的交互组件,亲自体验 TDD 的完整循环:
test('add(1, 2) 应该返回 3', () => {
expect(add(1, 2)).toBe(3)
})3.2 TDD 的三条规则
- 不写任何产品代码,除非是为了让一个失败的测试通过
- 只写刚好让测试失败的测试代码(编译不过也算失败)
- 只写刚好让测试通过的产品代码
3.3 TDD 的真正价值
TDD 的价值不仅在于"先写测试",更在于它迫使你思考接口设计。当你先写测试时,你是站在"使用者"的角度思考:这个函数应该接收什么参数?返回什么结果?这自然会导向更好的 API 设计。
TDD 不是银弹
TDD 适合逻辑密集的代码(算法、业务规则、数据转换),但对于 UI 布局、探索性原型等场景,强制 TDD 反而会拖慢速度。关键是理解它的思想,灵活运用。
4. 测试策略选择
4.1 不同项目的测试重点
| 项目类型 | 测试重点 | 推荐比例 |
|---|---|---|
| 工具库/SDK | 单元测试为主 | 90% 单元 + 10% 集成 |
| API 服务 | 集成测试为主 | 30% 单元 + 60% 集成 + 10% E2E |
| Web 应用 | 均衡分布 | 50% 单元 + 30% 集成 + 20% E2E |
| MVP/原型 | 关键路径 E2E | 少量核心测试即可 |
4.2 常用测试工具
| 工具 | 类型 | 适用场景 |
|---|---|---|
| Vitest | 单元/集成 | Vite 项目首选,兼容 Jest API |
| Jest | 单元/集成 | Node.js 生态最流行 |
| Playwright | E2E | 跨浏览器,微软出品 |
| Cypress | E2E | 开发体验好,调试方便 |
| Testing Library | 组件测试 | 以用户视角测试 UI 组件 |
5. AI 助力:用大模型提升测试效率
大模型在测试领域的能力已经非常强大——它可以帮你生成测试用例、发现边界条件、甚至写出完整的测试代码。
5.1 生成单元测试
提示词:
请为以下函数编写单元测试,使用 Vitest 框架,要求: 1. 遵循 AAA 模式(Arrange-Act-Assert) 2. 覆盖正常路径、边界条件和错误路径 3. 每个测试用例有清晰的中文描述 [粘贴你的函数代码]
5.2 发现边界条件
提示词:
分析以下函数,列出所有可能的边界条件和极端输入场景, 包括:空值、零、负数、超大数、特殊字符、并发情况等。 对每个场景说明预期行为和可能的风险。 [粘贴你的函数代码]
5.3 从需求生成测试(TDD 辅助)
提示词:
我要实现一个购物车模块,需求如下: - 添加商品、删除商品、修改数量 - 自动计算总价(含折扣) - 库存不足时提示错误 请按照 TDD 思路,先写出测试用例(不写实现), 使用 Vitest,覆盖所有核心场景。
AI 使用建议
AI 生成的测试要检查断言是否有意义——避免 expect(true).toBe(true) 这种无效测试。好的测试应该在代码出错时真的能失败。
6. 总结
- 测试金字塔:底层多、顶层少,平衡速度与真实度
- 单元测试:遵循 FIRST 原则和 AAA 模式,测试核心逻辑
- TDD:红绿重构循环,用测试驱动设计
- 策略选择:根据项目类型和阶段,选择合适的测试比例
终极思考
测试不是负担,而是加速器。短期看,写测试确实多花了时间;长期看,它节省了无数次手动验证、排查回归 Bug、以及深夜紧急修复的时间。好的测试让你有信心说出那句话:"放心改,测试会告诉我们有没有问题。"
延伸阅读
- 经典书籍:Kent Beck《测试驱动开发》是 TDD 的开山之作。
- 实用指南:尝试用 Vitest 为一个小项目写测试,体验从零开始的测试流程。
- 测试模式:了解 Mock、Stub、Spy 的区别和使用场景。
- 持续集成:将测试集成到 CI/CD 流水线中,每次提交自动运行。
