Skip to content

测试策略

前言

你的代码真的"没问题"吗? 每次改完代码手动点一遍看看有没有坏——这种方式在项目小的时候还能凑合,但当代码量增长到几万行、团队扩展到十几人时,"手动点点看"就是一场灾难。

本章带你理解软件测试的核心策略,从测试金字塔到 TDD,建立系统化的质量保障思维。

这篇文章会带你学什么?

章节内容核心概念
第 1 章测试金字塔测试的层次与比例
第 2 章单元测试实战如何写好一个测试
第 3 章TDD 驱动开发红绿重构循环
第 4 章测试策略选择不同场景的方案

学完本章,你将理解如何为项目选择合适的测试策略,写出有价值的测试,并通过 TDD 提升代码设计质量。


0. 全景图:为什么需要自动化测试?

想象你是一个建筑工程师。每次修改图纸后,你不会亲自爬上每一层楼去检查结构是否安全——你会依赖一套自动化的检测系统。软件测试就是代码世界的"结构检测系统"。

自动化测试的价值

  • 回归保护:修改 A 功能时,自动检测 B、C、D 功能是否被影响
  • 重构信心:有测试覆盖的代码,重构时心里有底
  • 活文档:好的测试就是最好的使用说明书
  • 快速反馈:几秒钟内知道代码是否正确,而不是等到部署后才发现问题

1. 测试金字塔:测试的层次与比例

1.1 三层金字塔

Mike Cohn 提出的测试金字塔是测试策略的经典模型。它告诉我们:不同类型的测试应该有不同的数量比例

通过下面的交互组件,点击金字塔的每一层,了解各层测试的特点:

交互式测试金字塔 ── 点击每一层查看详情
🖥️E2E 测试
🔗集成测试
🧪单元测试
越往上:越慢、越贵、越接近用户越往下:越快、越多、越接近代码

1.2 为什么是金字塔形?

金字塔形状反映了一个核心权衡:速度与真实度的取舍

  • 底层(单元测试):速度极快、数量最多、成本最低,但只能验证单个零件
  • 中层(集成测试):速度适中、数量适中,验证零件之间的配合
  • 顶层(E2E 测试):最接近真实用户,但速度慢、维护成本高、容易因环境问题失败

反模式:冰淇淋甜筒 —— 如果你的项目 E2E 测试最多、单元测试最少,那就是倒过来的"冰淇淋甜筒"。这意味着测试套件运行缓慢、经常失败、维护成本极高。


2. 单元测试实战

2.1 什么是好的单元测试?

好的单元测试遵循 FIRST 原则:

原则含义说明
Fast快速毫秒级完成,开发者愿意频繁运行
Independent独立测试之间互不依赖,可以单独运行
Repeatable可重复任何环境下运行结果一致
Self-validating自验证结果是明确的通过/失败,不需要人工判断
Timely及时在写代码的同时(或之前)写测试

2.2 测试的结构:AAA 模式

每个测试都应该有清晰的三段式结构:

javascript
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 的完整循环:

TDD 红绿重构循环 ── 点击"下一步"推进
🔴Red
🟢Green
🔵Refactor
第 1 步 / 5🔴 Red — 先写一个失败的测试
需求:实现 add(a, b) 函数。TDD 第一步不是写实现,而是先写测试。
add.test.js
test('add(1, 2) 应该返回 3', () => {
  expect(add(1, 2)).toBe(3)
})
❌ 测试失败 — add is not defined

3.2 TDD 的三条规则

  1. 不写任何产品代码,除非是为了让一个失败的测试通过
  2. 只写刚好让测试失败的测试代码(编译不过也算失败)
  3. 只写刚好让测试通过的产品代码

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 生态最流行
PlaywrightE2E跨浏览器,微软出品
CypressE2E开发体验好,调试方便
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. 总结

  1. 测试金字塔:底层多、顶层少,平衡速度与真实度
  2. 单元测试:遵循 FIRST 原则和 AAA 模式,测试核心逻辑
  3. TDD:红绿重构循环,用测试驱动设计
  4. 策略选择:根据项目类型和阶段,选择合适的测试比例

终极思考

测试不是负担,而是加速器。短期看,写测试确实多花了时间;长期看,它节省了无数次手动验证、排查回归 Bug、以及深夜紧急修复的时间。好的测试让你有信心说出那句话:"放心改,测试会告诉我们有没有问题。"


延伸阅读

  • 经典书籍:Kent Beck《测试驱动开发》是 TDD 的开山之作。
  • 实用指南:尝试用 Vitest 为一个小项目写测试,体验从零开始的测试流程。
  • 测试模式:了解 Mock、Stub、Spy 的区别和使用场景。
  • 持续集成:将测试集成到 CI/CD 流水线中,每次提交自动运行。