4 年后,我再次入门 TDD
0x01 引言
你好,我是以卖码为生的海门。今天想和大家探讨一下应该从哪里着手 TDD,以及要进行哪些类型的测试。本文及以后的文章都将使用 Jest 作为我们的测试工具。
刚毕业时,进了一家小微企业,这时不要求为代码写单测,我也不知道 TDD 的存在。直到进入第二家公司遇到了中仁并认识了亦乐,我们一起运营 freeCodecamp 社区,才有幸参加了一次由中仁发起的 TDD 工作坊,这是我第一次认识它,但并不会使用。我的第一次入门应该算是参加了熊节老师的训练营,可课程结束后,我学会了盲打,识别部分坏味道和重构手法,使用大部分快捷键,但我依然觉得自己不知如何去写 Unit Test,这是我再次学习和入门的原因。
0x02 让我们开始吧
第一步,我不会告诉你怎么安装开发环境,我只会告诉你怎么验证你的环境是否安装正确。
金丝雀测试,是最简单的测试,它的作用就是验证开发环境是否安装正确,确保正确开始。它存在的意义因此受到了质疑,但我认为保留它是有必要的。比如当我们换了新的工具或环境,它可以帮助我们快速的验证环境的正确性。
0x03 举个栗子:回文项目
在正式编码前,我会告诉你,入门 TDD 我们需要编写三种类型的测试:
- 正向测试:当前置条件满足时,验证代码的结果确实符合预期;
- 反向测试:当前置条件或输入不符合要求时(如:边界情况,非法输入),代码能优雅地进行处理,可以检查系统的容错能力和可靠性;
- 异常测试:代码在应该抛出异常的地方正确地抛出了异常。
我还会告诉你,每个单测都遵循 3As 模式:
- Arrange:准备数据;
- Act:执行待测函数;
- Assert:断言结果。
最后,别人闭口不提的,我也会告诉你:
- 测试套件(关键字 describe)是一组相关测试的集合,这组测试可以是验证一个函数,也可以是验证一组密切相关的函数的行为;
- 测试套件和测试用例的名称都需要简洁明了,且测试用例的名称要清楚表达测试目的和期望得到的结果,因为单测只是为了验证代码的正确行为,而我们的测试名称,描述、表达并记录了代码的行为,或者说原始需求;
- 当使用 3As 模式时,为了让代码看着舒服,不同的部分之间用空行隔开,另外每个单测里边的代码也需要保持简短,比如能用一行代码解决的事,也就不需要这个三部分了;
- 要是一个单测代码冗长复杂,它可能表达出了两种意思:一是测试的代码写的不好,二是待测函数的设计不行;
- 关注行为而非状态,避免为获取、设置状态的函数编写测试。从有业务价值的、有意思的行为开始编写。
在这个过程中对那些必要的状态进行设置和获取。
Emm…可以写代码了吗?
首先,我通过分析需求,拆解出了若干任务列表,根据某个任务列表将迅速想到的用例记录在测试列表中,如下:
- mom 是回文
- momu 不是回文
- dad 是回文
- 空字符串不是回文
- 两个空格串不是回文
- 不传参,抛出参数非法异常
记住,测试列表需要不断的去完善它,因为你每前进一步,也许会想到新的问题。
好嘞,我们的第一个正向测试就出来了:
嘿,好家伙!这个测试么有通过。来看下 Jest 给我们反馈的信息:
isPalindrome 函数没有实现或者还没有导入,那接下来我们将实现它并导入到测试文件中。
大家可以看到,我并没有一口气实现这个方法,而是使用初始测试驱动函数的接口设计,目的是为了让代码更具表现力和可读性。此时我们需要考虑: 函数名是否清晰描述代码功能 形参个数和名称是否满足需求 返回值类型是否正确 等…
让测试驱动设计,能够让我们将想到的问题提出来,帮助发现细节,然后在这个过程中梳理出代码的接口,还有可能找出需求的缺陷。
现在函数接口已经确定下来了,接下来的单测和实现,我将按照 TDD 的开发流程编写剩下所有的代码。剩下的代码就不贴出来了,需要的在 JestJsApp 自提,可以的话留个小星星哈!
Red - Green - Refactor Workflow:
- 编写测试用例;
- 运行测试,无法通过;
- 实现函数,测试用例通过;
- 优化代码,完成开发;
- 重复上述步骤。
0x04 代码覆盖率报告
代码覆盖率报告是很有价值的,但是也不能过度依赖它。
有价值的方面:
- 得到老板的夸奖;
- 能快速标识出哪些代码没有被测试覆盖到。
不过度依赖它,是因为它实际的数值并不特别重要,通常只有糟糕的数据令我们头疼,但良好的数据我们也要引起警觉。
比如,我某个文件的覆盖率是 98%,远远高于公司定的 80% 的标准,但是就是有一行代码没有被覆盖到,敢说这是合格的测试吗?此时,我们更需要关注的是检查哪行代码没有被测试覆盖,同时确保在修改代码时覆盖率数值没有降低。
如果你认为每一行代码都覆盖到了,就值得高歌一曲,我要告诉你代码都覆盖了也不一定是编写了充分的测试。我们可以从未覆盖的代码发现设计的缺陷,但我们不能从全部覆盖的代码上看到问题,所以我们还需要做代码和测试的审查来弥补。
0x05 总结
测试先行,有助于完善代码设计;小步迭代,有助于快速获取反馈重构代码。
最后,我们发现:
- 测试必须快速,以便我们快速获取反馈;
- 自动化校验,解放双手;
- 相互独立,测试结果互不依赖;
- 可重复执行n次且结果一致。
上边这四点说的就是著名的 F.A.I.R 原则!
0x06 参考
- 《JavaScript测试驱动开发》
- Jest 官网