---
title: "测试如何加速你的开发"
url: https://clean99.github.io/zh/2022/08/18/How-Testing-Speeds-Up-Your-Development/
date: 2022.08.18
lang: zh
---

作为前端开发者，我们大多数人都不喜欢写测试，因为"它会拖慢开发进度"。我以前也不写测试，我能理解你在为一个"快速迭代"的前端项目编写测试时的痛苦。但我确实想告诉你，在大多数情况下，测试可以加速你的开发，而不是拖慢它——除非你写的是错误的测试。我有一篇文章 [Some Common Mistakes in React Testing](/zh/2022/07/24/Some-Common-Mistakes-in-React-Testing/) 展示了哪些做法会让你在开发过程中感到痛苦。而在这篇文章中，我想向你展示**为什么正确的测试能加速你的开发。**

## 什么是开发？

我们是开发者，每天都在开发软件。但说真的，什么是开发？我们在日常工作中实际上做的是什么？<br />在《修改代码的艺术》一书中，作者认为开发的本质是行为变更。开发过程有四种类型：

1.  添加功能
1.  修复 Bug
1.  改进设计
1.  优化资源使用

每种类型都需要不同种类的行为变更：<img alt="development type" src="/img/testing/developmenttype.png">如你所见，我们在添加新功能时希望在软件中增加新行为，在修复 Bug 时希望改变旧行为。但当我们进行优化或重构时，通常不希望行为发生变化。如果我们在软件行为中引入了意外的变化，我们称之为**回归（regression）**。

### 软件腐化

我们大多数人都有这样的经历：在一个遗留项目（一个没有良好代码结构和测试的大型项目）中修复 Bug 或添加新功能，比从零开始做一个新项目更加痛苦和困难。这是因为**随着我们不断添加新功能，软件会日益腐化**。代码将变得非常复杂，最终极难维护。幸运的是，我们有 React / Vue 框架将网站**模块化**为页面，再将页面模块化为组件。所以通常不会让每个组件都变得非常复杂，但到了某个时间点还是会的。即便是代码质量最高的团队也无法阻止软件腐化，因为**解耦并不意味着你完全不会引入耦合**。

### 维护越来越难

因此，当行为变得越来越复杂时，我们会发现在确保现有行为不变的前提下进行开发变得愈加困难。当我们在组件中添加行为时，state 或 DOM 结构都会发生变化。如何保证**现有行为得到保留**？如何保证**新行为按我们预期执行**？以及，如何保证**新行为能与现有行为良好协作**？<br />**测试能给你信心。**
<img alt="preserving behavior" src="/img/testing/preservingbehavior.png">

## 测试的角色

测试在开发中可以扮演两种角色：一种是验证，另一种是防止回归。这两种测试可以在不同的开发生命周期中帮助你以**信心**开发并**提速**。

### 验证性测试

当我们向程序中添加新行为时，我们想知道新行为是否符合预期，怎么做呢？大多数前端开发者会编写代码，然后去本地开发环境检查行为变化。这就叫做手动测试。那么这种测试有什么问题呢？为什么自动化测试比手动测试更有效？这里有一个例子：<br />你开发了一个小组件，它在一系列步骤之后才会出现。假设我们要开发一个消息框，它会在提交一个需要填写 20 个字段的表单之后显示。<br />如果你使用手动测试：

- 你需要**填写 20 个字段（点击、点击、输入……）**，就像你的用户一样，然后点击提交。
- 你需要**祈祷服务器（包括你的本地服务器和后端）在测试过程中不会崩溃**。
- 你需要**祈祷依赖项（在本例中是 Form 组件）能正确工作**。
- 你需要**祈祷整个流程能一次性正确运行**。如果不能，猜怎么着？你需要调试与整个流程相关的组件，包括但不限于 Form 组件、API 和消息框。
- 你的同事审查你的代码并要求你修改部分代码，你需要用周全的测试用例再次进行点击输入的循环。真的很痛苦！

如你所见，手动测试就是在不停祈祷（开个玩笑）。当依赖项越来越多，组件本身越来越复杂时，情况会变得更加糟糕。<br />那么自动化测试怎么样？<br />你只需**编写几行测试代码**来验证这个消息框是否会根据相应的响应状态码显示正确的消息。然后你可以写一个使用一些**模拟（mock）**字段数据的测试，来验证它能以期望的数据和格式提交给后端。<br />我可以向你保证，编写这些测试并运行它们（对于典型的前端测试，通常只需几秒钟）所花费的时间，远少于你在本地开发环境中不断点击、输入、提交的时间。而且，如果你的测试失败，你可以知道哪个部分出了问题，而不是调试整个系统。并且你的测试可以运行**数千次**，这是自动化测试的一个显著优势。<br />**这一切的核心在于隔离。** 测试帮助你隔离每个组件，这样每次修改代码时，你都能获得**快速、精准的反馈**，而只需多写几行代码。

### 回归测试

验证性测试用于验证我们的新行为，而回归测试则是为了**确保我们在修改代码时旧行为不会改变**。<br />你刚加入一个新团队，你的老板并不太喜欢你，他给了你一些工单，要求你在一个"遗留"项目中修复 Bug 并添加新功能。当你第一次查看项目代码库时，你被那混乱的结构惊到了，而且它没有任何测试和文档！所以你不得不做大量研究（通过逐一运行项目功能）来检查这个项目能做什么，以及哪些部分出了问题。<br />几天后，你鼓起勇气修改代码，通过修改某些 state hook 修复了一个 Bug。但猜怎么着，你**仍然很有可能引入新的 Bug**，因为你不知道这个 state hook 的用途。<br />所以如果项目之前已经写好了测试，你需要做的就是查看测试用例，因为**测试用例可以作为文档**，你可以了解项目（组件）在特定情况下会做什么。而且如果你修改了代码，在运行完所有测试后，你可以有**信心**地说**你没有破坏重要功能**。此时测试就充当了**回归测试来防止回归**。<br />如果你接手的项目有测试，那你很幸运。而对于没有任何测试的项目，你仍然可以自己添加，以**覆盖你想要修改的代码周围的行为**。

## 其他优势

自动化测试还能给你带来一些其他好处，以下是其中一些：

- **文档**：测试可以作为项目的文档，因为它模拟了用户如何与项目交互以及项目如何响应。
- **迫使你理解需求**：如果你不知道需求是什么，你就无法编写测试，因为测试本质上是将人类语言翻译成脚本。而如果你使用 [Testing Best Practice Tdd](/zh/2022/07/24/Testing-Best-Practice-Tdd/)，**你需要根据需求先编写测试**，这会更好地迫使你首先思考需求。
- **迫使你以良好的结构解耦代码**：如果你的代码是耦合的，所有的东西都写在一个单独的组件中，你就很难为它编写测试，因为你无法再隔离它们了。所以测试可以帮助你思考代码的**结构和设计是否合理**。
