# 测试策略 ::: tip 前言 **你的代码真的"没问题"吗?** 每次改完代码手动点一遍看看有没有坏——这种方式在项目小的时候还能凑合,但当代码量增长到几万行、团队扩展到十几人时,"手动点点看"就是一场灾难。 本章带你理解软件测试的核心策略,从测试金字塔到 TDD,建立系统化的质量保障思维。 ::: **这篇文章会带你学什么?** | 章节 | 内容 | 核心概念 | |-----|------|---------| | **第 1 章** | 测试金字塔 | 测试的层次与比例 | | **第 2 章** | 单元测试实战 | 如何写好一个测试 | | **第 3 章** | TDD 驱动开发 | 红绿重构循环 | | **第 4 章** | 测试策略选择 | 不同场景的方案 | 学完本章,你将理解如何为项目选择合适的测试策略,写出有价值的测试,并通过 TDD 提升代码设计质量。 --- ## 0. 全景图:为什么需要自动化测试? 想象你是一个建筑工程师。每次修改图纸后,你不会亲自爬上每一层楼去检查结构是否安全——你会依赖一套**自动化的检测系统**。软件测试就是代码世界的"结构检测系统"。 ::: tip 自动化测试的价值 - **回归保护**:修改 A 功能时,自动检测 B、C、D 功能是否被影响 - **重构信心**:有测试覆盖的代码,重构时心里有底 - **活文档**:好的测试就是最好的使用说明书 - **快速反馈**:几秒钟内知道代码是否正确,而不是等到部署后才发现问题 ::: --- ## 1. 测试金字塔:测试的层次与比例 ### 1.1 三层金字塔 Mike Cohn 提出的测试金字塔是测试策略的经典模型。它告诉我们:**不同类型的测试应该有不同的数量比例**。 通过下面的交互组件,点击金字塔的每一层,了解各层测试的特点: ### 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 的完整循环: ### 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 流水线中,每次提交自动运行。