# 测试策略导论

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

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

**这篇文章会带你学什么？**

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

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

---

## 0. 全景图：需要自动化测试的原理

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

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

---

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

### 1.1 三层金字塔

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

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

<TestPyramidDemo />

### 1.2 是金字塔形的原理

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

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

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

---

## 2. 单元测试实战

### 2.1 好的单元测试概述

好的单元测试遵循 **FIRST** 原则：

| 原则 | 含义 | 说明 |
|------|------|------|
| **F**ast | 快速 | 毫秒级完成，开发者愿意频繁运行 |
| **I**ndependent | 独立 | 测试之间互不依赖，可以单独运行 |
| **R**epeatable | 可重复 | 任何环境下运行结果一致 |
| **S**elf-validating | 自验证 | 结果是明确的通过/失败，不需要人工判断 |
| **T**imely | 及时 | 在写代码的同时（或之前）写测试 |

### 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 的完整循环：

<TDDCycleDemo />

### 3.2 TDD 的三条规则

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

### 3.3 TDD 的真正价值

TDD 的价值不仅在于"先写测试"，更在于它**迫使你思考接口设计**。当你先写测试时，你是站在"使用者"的角度思考：这个函数应该接收什么参数？返回什么结果？这自然会导向更好的 API 设计。

::: tip 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，覆盖所有核心场景。
> ```

::: tip AI 使用建议
AI 生成的测试要检查断言是否有意义——避免 `expect(true).toBe(true)` 这种无效测试。好的测试应该在代码出错时真的能失败。
:::

---

## 6. 总结

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

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

---

## 延伸阅读

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