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