使用反射提高单元测试的质量引发的思考
在用反射做UT时,注意,使用的是getDeclaredMethod方法而不是getMethod,同时注意使用method.setAccessible(true)来使方法可以被访问。在实现的时候,最好有一个方法的转调,以减少代码重构对test的影响(这样只有一个地方hardcode)。
比如我们有一个
privateintgetUserAge(StringuserId)的方法,为了测试......
UT还private?更进一步地说,UT还纠结于单元测试?
如果你真的做UT,你在动手写代码之前就有关于用户行为(这里的用户不仅仅指最终用户,如果你是发布一组工具那么你的用户就是其它开发组中的程序员)的设计了,你可以将这种设计写为测试程序(而不是满足在可以随便忽悠着解释的文档),然后才开始开发。如果编译不通过,就让它编译通过;如果针对数据输入输出的断言不通过,就让断言通过!
你做UT时还纠结于private,甚至纠结于单元测试,咄咄怪事。(因为单元测试工具只是一个驱动测试开始执行的工具,而你要的是针对UT的对象而不是什么代码函数单元啊)。
更进一步说,好的测试是对重构提供支持的。你纠结于那些“单元”,还能重构吗?使用你的所谓单元测试代码维系垃圾代码,当作餐桌上的主菜,是不是本末倒置了?
我给你提供一个测试的“原则”作为参考。对于纯粹功能性的开发,我在写测试时保持大致1个半小时的开发时间(因为之前需要10~30分钟编写测试代码的时间),这就是我写测试的原则。
每当重构了,当然要回归运行那些测试代码。因此这些测试时为了支持重构这种高度灵活性的东西而准备的。
怎么可能像你那样,先写完了实现代码,然后才钻到垃圾堆底层private单元集合里边去翻最臭味道的代码单元来编织到“测试系统”里呢?
你说的这些我都是同意的,如果条件允许的话,我当然也是这么做,事实上,TDD的开发精髓就是测试驱动设计。
问题是我现在面临的场景中,业务逻辑很复杂,于是,为了方法的内聚性,一个public的方法往往需要调用拆分出去多个private方法,这是,如果坚持一套完整的测试的话,那么测试环境的搭建将变得复杂,或者说关注点不集中,因为测试的分支是成指数级别增长的,而为了系统的稳定性,我们需要覆盖掉所有的分支。我选择用反射来测试这些private方法可以分离测试的关注点。当然,这个对重构是有不好的影响的,权衡利弊,我只能采取一些措施降低这种影响,但是还是要保全一个test的完整性和专注性。
举个例子,
publicObjectf(Objectinput){
根据input计算出s1,s2,s3,s4,flag
if(flag){
a=g(s1,s2);
returna;
}else{
b=g(s3,s4);
returnb;
}
}
publicvoidf1(){
调用g.
}
privateObjectg(Objecto1,Objecto2){
}
flag有2种状态。
假设g方法的参数o1,o2各有3种状态。
如果采用先test后code的方法,为了完整的测试f的功能,那么我们需要控制input来生成所有的2*3*3=18个test才能完全覆盖到所有的分支。当然,也可以控制input生成flag为true的9种情况,以及1个flag为false的情况。
但是我觉得这样的测试关注点不够集中,我的思路是,可以先为f写两个test,来覆盖flag的两个分支,然后coding,保证f的两个分支都被测试到,即g都被调用过,至于g自己的方法约定,应该属于g自己的事情,可以用反射来测试。
我们在考虑一下如果f,f1都要调用g方法的情况,那么我们有必要为测试f,f1都建立g的9种状态吗?如果还有更多的f2,f3,...fn呢?
传统的test是一种黑盒测试,而用反射是白盒测试。我认为程序员是可以利用关于实现的知识进行测试的。利用反射,虽然一方面增加了重构的难度,但是,可以提高测试的关注点,同时,白盒测试也有利于重构出被多个public方法调用的private方法。