单元测试作为保障代码质量的有效手段之一,有以下优势:1、减少BUG,释放资源;2、 为代码重构保驾护航;3、 既是编写单测也是CodeReview;4、便于调试与验证;5、驱动设计与重构。
一、 什么是单元测试
“在计算机编程中,单元测试又称为模块测试,是针对程序模块来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类、抽象类、或者派生类中的方法。”
摘录来自维基百科
单元测试(Unit Testing)顾名思义就是测试一个单元,这里的单元通常指一个函数或类,区别于集成测试中的模块和系统。集成测试的测试过程通常存在跨系统模块的调用,是一种端到端的测试;而单元测试关注对象的颗粒度较小,用来保障一个类或者函数是否按照预期正确的执行。
二、为什么要写单元测试
作为保障代码质量的有效手段之一,公司也在积极的推进单元测试。结合单测的实践,总结了以下几点单元测试的好处,认真实践过的同学,应该会有共鸣。
1、减少BUG,释放资源
上面这张图,旨在说明两个问题:
- 85%的缺陷都在代码设计阶段产生;
- 发现bug的阶段越靠后,耗费成本就越高,呈指数级别的增长。
单元测试是所有测试环节中最底层的一类测试,是名列前茅个环节,也是最重要的一个环节。大多数缺陷是Coding阶段引入,修复的成本随着软件生命周期进展不断上升。日常研发中,在交付测试前我们对功能单元进行主流程、各种边界及异常单元测试的编写,能有效帮助我们发现代码中的缺陷。相对于后期来自测试同学或者线上异常反馈,再来进行排查定位、修复发布的成本来说,单元测试的性价比是极高的。单元测试可以有效地保障代码质量,给我们带来质量口碑的同时,也为他人和自己减少因修复低级BUG而投入的时间,能够将精力分配到其他更有意义的事情上。
2、 为代码重构保驾护航
面对项目中历史遗留的腐化代码,我们都有推倒重来的冲动,但它毕竟经过了长时间的稳定性考验,我们又担心重构之后出现问题。这是我们经常会遇到的境况,当要重构不是非常熟悉的祖传代码,又没有充足的测试资源保障的时候,重构引入缺陷的风险还是很大的。
那如何保证重构不出错呢?Martin Fowler在《重构:改善既有代码的设计》提到:
重构是很有价值的工具,但只有重构还不行。要正确地进行重构,前提是得有一套稳固的测试集合,以帮我发现难以避免的疏漏。即便有工具可以帮我自动完成一些重构,很多重构手法依然需要通过测试集合来保障。
除了需要对业务流程有足够的了解并且熟练掌握各种设计思想、模式之外,单元测试是保证重构不出错的有效手段。当重构完成之后,如果新的代码仍然能通过单元测试,那就说明代码原有正确的逻辑未被破坏,原有的外部可见行为没有发生改变。单元测试给了我们重构的信心与底气。
3、 既是编写单测也是CodeReview
单元测试和CR是保障代码质量行之有效的两个手段。在研发交付过程中,通常我们提交CR的时机较为滞后,评审同学指出待优化或修复的时间点也较晚,修复的风险和成本上都有所增加。
我们编写编码单元测试过程,其实也是自我CodeReview的过程。在这个过程中,我们对功能单元主流程、边界及异常进行测试,也在自我审视代码的规范、逻辑及设计。既提高了后续提交CR的质量与评审效率,也将问题提前暴露。
4、便于调试与验证
当项目存在多个协同方时,我们只需按照约定mock出依赖项的数据,无需等所有依赖的应用接口开发部署完成后再进行调试,提高了我们协同的效率与质量。我们将功能需求进行拆解,在开发完每一个小功能点时,即可进行单元测试的编写与验证,这种习惯能让我们对编码得到快速的验证反馈;同时,在开发完整个功能时,我们需要跑一遍项目所有的单测用例,可以清晰的感知,本次整个功能需求的改动是否对已有业务case造成影响。
如果我们能够保障每个类、函数都能通过单元测试按照预期业务逻辑执行,那整合后的功能模块或系统,出问题的概率都能大大降低。从这个意义上讲,单元测试也对集成测试、系统测试做了有力的支撑。
5、驱动设计与重构
设计和编码的时候,我们很难将所有的问题都想清楚。那我们知道,评判代码质量重要的的标准之一就是代码的可测性。如果对一段代码进行单测,发现难于编写,需要编写的case非常多,或者当前的测试框架无法mock依赖对象,需要依赖其他具备高级特性的测试框架时,我们需要回过头来审视代码,是否编码设计得不合理,导致代码的可测性不高。这是个正反馈的过程,让我们有针对性的进行重新设计与重构。
延伸阅读:
三、什么是有效的单元测试
可读、可维护、可信赖、快速执行!《单元测试的艺术》中描述优异单元的特性:
- 它应该是自动化的,可重复执行;
- 它应该很容易实现;
- 它应该第二天还有意义;
- 任何人都应该能一键运行它;
- 它应该运行速度很快;
- 它的结果应该是稳定的(如果运行之间没有进行修改的话,多次运行一个测试应该总是
- 返回同样的结果);
- 它应该能完全控制被测试的单元;
- 它应该是完全隔离的(独立于其他测试的运行);
- 如果它失败了,我们应该很容易发现什么是期待的结果,进而定位问题所在。
可读性
“一般程序员写得出计算机能读懂的代码。优异程序员写得出人能读懂的代码” — 马丁·福勒可读的代码才是可维护的;难以阅读和理解的测试用例,最终的结果就是删掉它,因为维护成本过高。可读性高于纯粹的性能。
可维护性
团队内使用一套范式的结构,有助于使之更好用,快速定位问题;消灭代码中的坏味道。
可信赖
可信赖的含义:
- 测试可重复;
- 测试与依赖环境隔离;
- 只测试不进行验证是不可靠的测试;
- 在测试类中不要依赖与测试的顺序;
- 测试的结果是精准的:校验的精准以及错误问题的精准定位;
快速执行
保证单测快速执行,缩短反馈时长;
为什么有效的单元测试如此重要
无效的单元测试是没有意义的,反而会增加维护成本,最终导致单元测试的失败!
如上图所示,坐标中任意一个点,其与横纵坐标垂直线所形成的矩形面积代表CI为团队带来的价值,那么在我看来有两个关键的因素:横坐标是单元测试的基础能力建设,纵坐标则是有效的单元测试;没有有效的单元测试,基础能力做出花来也毫无意义!完善的基础能力同时也帮助我们更低成本的写出有效的单元测试。