引言

前期完成了一个较大的模块,即将上线,最近在补充单测。补充单测的过程中,不但发现了几个bug,还发现之前的实现里面业务代码和黏结模块的代码耦合太紧以致于不便扩展和测试。为此,这两天总结下关于单测的感悟。


为啥要单测

单测不是为了追求覆盖率和KPI,那是为了什么?

首先是为了验证函数和模块功能的正确性,其次是为了避免改动引入新的bug,再者把发现的bug的解决方案放到单测里验证,可以避免再次出现类似问题。

理论上,每个模块、功能都需要单测覆盖,特别是涉及关键业务逻辑、复杂判断过程、高危高频出错模块的,都应该高优用单测覆盖。这样,新feature的开发、旧功能的完善的代码,都可以通过跑之前的单测来验证是否兼容已有的功能 。

养成这样一个习惯后,我们就可以白天coding,晚上让机器自动跑单测,第二天检查是否发现问题并解决,流程上保证不用担心后面的实现打破了前面的限制可能引入新的bug。 同时,这样持续下去,不断看到一个个模块越来越稳定,一个个单测PASS,被就会给自己一个个正向激励,提高工作效率和兴趣,进而避免周末或者节假日临时突击bug。


哪些地方必须用单测覆盖

一个业务功能通常由很多模块组成,而一个模块又可以切分成很多子模块,每个子模块还可以继续切分,直至到一个个具体实现的对象,而每个对象又有很多接口和方法,那么是不是所有的方法都需要单测覆盖?如果不是,哪些模块必须高优单测覆盖呢?

结合最近项目上发现的问题和最近单测的感悟,总结下来,这些地方需要高优单测覆盖:

集成测试多次出现问题的业务流程

比如笔者设计和实现的一个随机写入的模块,它要求支持强一致性的多副本写入和恢复。它的写入流程相对简单,但它的恢复过程需要禁止源端(甚至其他副本)的写入,同时还需要考虑到业务模块重启、节点掉电重启等异常场景,还需要和主控节点和上层代理模块进行交互,在集成测试过程中多次出现多副本数据不一致的问题。这里就需要梳理所有的case, 用单测覆盖,否则很难避免再次出现问题,也不能放心是否还有没被cover的场景。


业务逻辑相对复杂的模块或方法


比如笔者实现的1个裸盘上顺序写入的模块,因为支持了乱序写入有序ACK,业务逻辑比较复杂,再加之要保证重启能够自动恢复数据上次ACK的位置,需要精确的计算和实现。这里就需要高优单测覆盖。


业务影响大的模块

比如在分布式存储系统中,涉及到数据正确性、多副本数据一致性、数据版本一致性的,都需要考虑单测覆盖。


用例如何写好

那么上面这么多地方需要高优单测覆盖,我们该如何设计并实现一个好的单测用例呢?


聚焦关键业务

比如测试的是避免重复申请同1个存储块资源,那么就应该忽略业务流程上相关的黏结模块(比如brpc模块、线程模型模块、异步调用模块),而保留相关的存储块管理模块,通过各种创建操作、删除操作的组合,验证预期的业务逻辑是否实现。

善于绕过黏结模块

针对上面提到的brpc模块、线程模型模块相关,单测中如果不想办法处理就可能影响单测(当然这主要还是业务模块和黏结模块没有解耦导致的),那怎么办?

一是解耦。

把业务模块和黏结模块没有解耦的地方重构,抽象出1个业务接口,单测只覆盖业务接口。

二是mock。

mock掉上面的黏结模块的接口。比如异步周期调度模块,后面直接mock成直接调用接口的功能;比如裸盘的读写,可以mock成对本地文件的读写。