# 4 年后，我再次入门 TDD


## 0x01 引言
你好，我是以卖码为生的海门。今天想和大家探讨一下应该从哪里着手 TDD，以及要进行哪些类型的测试。本文及以后的文章都将使用 Jest 作为我们的测试工具。

刚毕业时，进了一家小微企业，这时不要求为代码写单测，我也不知道 TDD 的存在。直到进入第二家公司遇到了中仁并认识了亦乐，我们一起运营 freeCodecamp 社区，才有幸参加了一次由中仁发起的 TDD 工作坊，这是我第一次认识它，但并不会使用。我的第一次入门应该算是参加了熊节老师的训练营，可课程结束后，我学会了盲打，识别部分坏味道和重构手法，使用大部分快捷键，但我依然觉得自己不知如何去写 Unit Test，这是我再次学习和入门的原因。

## 0x02 让我们开始吧

第一步，我不会告诉你怎么安装开发环境，我只会告诉你怎么验证你的环境是否安装正确。

金丝雀测试，是最简单的测试，它的作用就是验证开发环境是否安装正确，确保正确开始。它存在的意义因此受到了质疑，但我认为保留它是有必要的。比如当我们换了新的工具或环境，它可以帮助我们快速的验证环境的正确性。

[![NYggH0.png](https://s1.ax1x.com/2020/06/22/NYggH0.png)](https://imgchr.com/i/NYggH0)

## 0x03 举个栗子：回文项目
在正式编码前，我会告诉你，入门 TDD 我们需要编写三种类型的测试：
1. 正向测试：当前置条件满足时，验证代码的结果确实符合预期；
2. 反向测试：当前置条件或输入不符合要求时（如：边界情况，非法输入），代码能优雅地进行处理，可以检查系统的容错能力和可靠性；
3. 异常测试：代码在应该抛出异常的地方正确地抛出了异常。

我还会告诉你，每个单测都遵循 3As 模式：
1. Arrange：准备数据；
2. Act：执行待测函数；
3. Assert：断言结果。

最后，别人闭口不提的，我也会告诉你：
1. 测试套件（关键字 describe）是一组相关测试的集合，这组测试可以是验证一个函数，也可以是验证一组密切相关的函数的行为；
2. 测试套件和测试用例的名称都需要简洁明了，且测试用例的名称要清楚表达测试目的和期望得到的结果，因为单测只是为了验证代码的正确行为，而我们的测试名称，描述、表达并记录了代码的行为，或者说原始需求；
3. 当使用 3As 模式时，为了让代码看着舒服，不同的部分之间用空行隔开，另外每个单测里边的代码也需要保持简短，比如能用一行代码解决的事，也就不需要这个三部分了；
4. 要是一个单测代码冗长复杂，它可能表达出了两种意思：一是测试的代码写的不好，二是待测函数的设计不行；
5. 关注行为而非状态，避免为获取、设置状态的函数编写测试。从有业务价值的、有意思的行为开始编写。

在这个过程中对那些必要的状态进行设置和获取。

Emm...可以写代码了吗？

首先，我通过分析需求，拆解出了若干任务列表，根据某个任务列表将迅速想到的用例记录在测试列表中，如下：
* mom 是回文
* momu 不是回文
* dad 是回文
* 空字符串不是回文
* 两个空格串不是回文
* 不传参，抛出参数非法异常

记住，测试列表需要不断的去完善它，因为你每前进一步，也许会想到新的问题。

好嘞，我们的第一个正向测试就出来了：

![NY2Mbq.png](https://s1.ax1x.com/2020/06/22/NY2Mbq.png)

嘿，好家伙！这个测试么有通过。来看下 Jest 给我们反馈的信息：

![NY2sPO.png](https://s1.ax1x.com/2020/06/22/NY2sPO.png)

isPalindrome 函数没有实现或者还没有导入，那接下来我们将实现它并导入到测试文件中。

![NY2UM9.png](https://s1.ax1x.com/2020/06/22/NY2UM9.png)

大家可以看到，我并没有一口气实现这个方法，而是使用初始测试驱动函数的接口设计，目的是为了让代码更具表现力和可读性。此时我们需要考虑：
函数名是否清晰描述代码功能
形参个数和名称是否满足需求
返回值类型是否正确
等...

让测试驱动设计，能够让我们将想到的问题提出来，帮助发现细节，然后在这个过程中梳理出代码的接口，还有可能找出需求的缺陷。

现在函数接口已经确定下来了，接下来的单测和实现，我将按照 TDD 的开发流程编写剩下所有的代码。剩下的代码就不贴出来了，需要的在 [JestJsApp](https://github.com/yihaimen/JestJsApp) 自提，可以的话留个小星星哈！

Red - Green - Refactor Workflow：
1. 编写测试用例;
2. 运行测试，无法通过；
3. 实现函数，测试用例通过；
4. 优化代码，完成开发；
5. 重复上述步骤。

## 0x04 代码覆盖率报告
代码覆盖率报告是很有价值的，但是也不能过度依赖它。

有价值的方面：
* 得到老板的夸奖；
* 能快速标识出哪些代码没有被测试覆盖到。

不过度依赖它，是因为它实际的数值并不特别重要，通常只有糟糕的数据令我们头疼，但良好的数据我们也要引起警觉。

比如，我某个文件的覆盖率是 98%，远远高于公司定的 80% 的标准，但是就是有一行代码没有被覆盖到，敢说这是合格的测试吗？此时，我们更需要关注的是检查哪行代码没有被测试覆盖，同时确保在修改代码时覆盖率数值没有降低。

如果你认为每一行代码都覆盖到了，就值得高歌一曲，我要告诉你代码都覆盖了也不一定是编写了充分的测试。我们可以从未覆盖的代码发现设计的缺陷，但我们不能从全部覆盖的代码上看到问题，所以我们还需要做代码和测试的审查来弥补。

## 0x05 总结
测试先行，有助于完善代码设计；小步迭代，有助于快速获取反馈重构代码。

最后，我们发现：
1. 测试必须快速，以便我们快速获取反馈；
2. 自动化校验，解放双手；
3. 相互独立，测试结果互不依赖；
4. 可重复执行n次且结果一致。

上边这四点说的就是著名的 F.A.I.R 原则！

## 0x06 参考
* 《JavaScript测试驱动开发》
* Jest 官网

![搜一搜](https://s1.ax1x.com/2020/06/08/tWbbz8.png)
