根据维基百科的解释,单元测试又称为模块测试。是针对程序单元来进行正确性校验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序,函数,过程等,对于面向对象编程,最小单元就是方法。
通常来说,程序员每修改一次程序就会进行最少一次单元测试,在编写程序的过程中前后可能要进行多次单元测试,以证实程序满足需求。
那为什么要做单元测试呢?
我们首先来看看Android程序员常见的自测方式:
实现某个功能后,在手机上执行整个应用,然后在手机上操作应用,在界面上多次点击后,进入使用该功能的场景,然后测试该功能,通常只会测试功能执行的主路径。也就是说如果功能有多分支结构(if-else),那么自测时只会测试一条主路径,其它分支结构都会交给测试人员进行人工测试。
那这样做有什么问题呢?
如果每次实现功能时都能确保没问题,那上述流程看起来就没啥大问题了。但事实上,我们每次实现某个功能时,谁都不敢保证写完功能实现代码一定能保证它一点问题没有,谁能这么说,我也只能呵呵了。实际上,我们每次实现的功能或多或少都会存在问题,我们需要反复修改代码,来测试我们的逻辑是否正常,按照上述的流程,我们修改完代码后需要编译生成apk,连接手机,将应用安装至手机,这几步通常就需要2分钟,然后再在手机上操作进入使用功能场景的地方,测试功能场景,并观察结果,这一般需要1分钟。所以我们每次写完代码后,需要3分钟进行验证,如果修改5次,每次定位问题并修改逻辑的时间为2分钟,那么确保该功能主逻辑没问题需要5*(2+3)=25分钟,其中60%的时间(15分钟)用于验证问题,40%的时间(10分钟)用于真正的解决问题。
从这个角度看,这种自测方式非常低效,还需要程序员不断在电脑上的编程IDE和手机的应用之间切换,并且还要在手机上反复执行重复操作,对于程序员来说,其实是一件很痛苦的事情,也很容易误操作。有时候等待手机连接到电脑还要等半天,尤其是装驱动有时候要装半天,甚至adb冲突导致连接也要等非长久,有时候公司要求归还开发用的手机,换一部新的开发机,使用新的开发手机会非常不熟悉,在手机上打开调试模式都要半天,这些都是非常耽误工作效率的事情,让程序员非常痛苦。
另外,这种测试通常只会测试功能执行的主流程,还有很多分支流程不会执行,交给测试人员执行时,因为测试人员通常都是做黑盒测试,所以对内部逻辑不会太了解,很容易遗漏某些分支的测试,并且这些分支的准备条件对于测试人员来说也很不好准备,所以即使知道,也会忽略某些分支的测试。而这些分支如果有问题,到了用户侧就会暴露出来了。所以这种测试没法保证功能的非主分支也能执行正常。
另外,这种测试方式即使让功能的初次实现没问题了,后续迭代过程中会不断修改它的逻辑,或者它依赖的逻辑,这时候问题就出来了,因为我们修改它依赖的逻辑后,并不会再让测试同学测试这个功能。我们可能都遇到过这种场景,我们将某个Bug修改好了之后,过一段时间后,我们修改另外一个Bug,这个Bug修改好了,结果前一阵子修改的Bug又出现了。这是为什么呢?因为我们修改Bug时通常都会专注这一个Bug,修改逻辑时,只会专注于将引起这个问题的逻辑调整正确,但是将引起这个问题的逻辑调整后之后,有可能导致其它问题。比如,如果函数A调用函数C,希望它返回"Hello",函数B调用函数C,希望它返回"Hello2",某一天函数A希望函数C返回"HelloC"了,于是修改了函数C的逻辑,结果函数A调用函数C确实返回了"HelloC",然而函数B调用函数C也返回"HelloC"了,那么这就有问题了。而我们可能并不会注意到这个问题,就直接发了版本,到了用户侧,就有大量用户抱怨了。如果我们每次发版本时,将所有曾出现过的Bug以及所有的功能都测试一遍,那么需要非常长的时间,会严重耽误项目进度。
我们再回到当初的问题,为什么做需要做单元测试,从上述几个角度已经看到通常的自测方式存在的问题,那我们看使用单元测试是如何解决这些问题的:
低效的问题
使用JUnit的单元测试时,可以脱离手机进行代码逻辑正确性的验证,也不需要在手机上操作,执行测试用例后直接输出测试结果是否正常,如果正常就是绿颜色的执行结果,如果失败就是红颜色的执行结果,可以将验证操作的时间缩短到30s内。当然我们需要花一定的时间在编写测试用例上,不过这只是一个一次性的工作。