从零开始写单元测试

 
关于单元测试的三个问题作为一个程序员,或多或少听说过单元测试,但很多小伙伴还没有在实际项目中用到 。究其原因,可能是对单元测试有一些「误解」,比如:

  • 写单元测试需要花费更多的时间,我每天写产品代码都要加班,哪来时间写测试;
  • 写单元测试收益不大,还不是一样有bug;
  • 写单元测试有负担,改产品代码的结构,还得去改测试代码 。
先尝试解答这几个问题 。
写单元测试会花费更多的时间,这点描述其实不准确 。准确地说,写单元测试需要花费更多「写代码的时间」,这点没什么可说的,毕竟要多写一些测试代码 。但一个程序员,做一个需求的时候,花在纯写代码的时间其实不多 。你得理以前代码的逻辑,设计类和方法,然后才是写代码,写完了再手动测试,可能有bug还要去debug,再修复,再测试 。「真正写代码的时间,其实是很少的」 。使用单元测试虽然可能占用了更多写代码的时间,但它可以帮你缩短其它时间,「会让你做这个需求花费的总时间更少」 。
写单元测试会有bug吗?当然可能有了,我们是无法做到真正的bug free的,但是单元测试写好了,可以显著减小bug的数量 。因为写单元测试发现bug的成本是非常低的,它可以在开发阶段就发现bug,而且可以测试很多边界的条件 。但如果你「对需求和业务的认知本来都是有误」的,这是单元测试解决不了的,自然会产生bug 。话说回来,写单元测试的收益远不止发现bug这么简单,它还具有「代码文档」的功能,以及「重构的安全网」存在 。甚至它还可以帮你「理需求」,「设计代码」 。
改产品代码需要维护对应的测试代码,这确实是带来了额外的成本 。不过借助编辑器的「重构功能」,可以比较方便地批量修改需要修改的地方,其实代价没有想象中的那么大 。而且重构后,再跑一遍单元测试,看哪些挂掉了,可以double-check你改的产品代码有没有问题 。如果我们把单元测试当成是「产品代码的文档」来看,大概就更能够接受这个维护的成本了 。
为什么需要单元测试?前面提到,单元测试有很多功能 。个人觉得单元测试最大的作用是“代码文档”和“重构安全网” 。毕竟软件开发的漫长过程中,总少不了修修改改 。如果没有足够的测试,改一段代码就像是在排地雷,改完后心里也总是打鼓,上线前需要先默默拜个神,生怕触发了什么bug 。
但如果有足够的测试(不只是单元测试),改完代码后可以跑一遍测试,看哪些挂掉了,是不是自己的改动导致的,该怎么修好测试 。这样心里就有底气多了 。
要知道,代码写出来是给人看的 。而测试比产品代码更友好,因为它简单,直白,站在使用者的视角来描述,所以如果想要了解一段产品代码具有什么功能,看它的单元测试会更直观,更舒服 。
很多团队会做测试,但绝大多数测试的工作是在开发后,由专门的测试同学去负责端到端的测试或者API测试 。其实端到端的测试成本是非常大的,尤其是对于某些边界条件,构造数据和场景是非常麻烦的 。而且一旦发现了bug,再去沟通,修改,提交,部署,需要花费很多时间 。
而单元测试最大的优势就是“成本低”,想要测试产品代码的每个分支都比较容易,而且单元测试一般是开发同学自己写,可以用最小的时间发现bug,用最低的成本修改bug 。
什么是单元测试?测试金字塔并不是所有测试都是单元测试,测试其实分成很多种 。业界比较广泛传播的“测试金字塔”描述了它们的区别和关系:
从零开始写单元测试

文章插图
 
测试金字塔
从测试金字塔模型来看,越在底层的测试,覆盖面应该更广,成本更低 。单元测试处于测试金字塔的最低端,是整个测试金字塔的基础 。
当然了,测试金字塔并不一定只有三层,中间可能会有其它的测试,比如“契约测试”等 。
?
还有另一种“菱形测试”模型的说法,即大多数测试写在接口测试这一层,而UI测试和单元测试只写小部分测试 。本文不做讨论,有兴趣的同学可以自行了解一下 。
?
单元测试的特点【从零开始写单元测试】单元测试就像它的名字一样,“单元”(Unit),足够小,足够快,无依赖 。单元测试只测你想测的那部分产品代码的逻辑,一个单元测试应该只测一个简单的业务逻辑 。一般来说,运行一个单元测试是很快的,基本上在几毫秒到几十毫秒之间 。如果有依赖的类,可以mock其他类,消除外部依赖 。


推荐阅读