在具体做单元测试的过程中,会遇到一些小问题,比如:
- 断言JavaBean或集合类
- 依赖DB数据
- 单元测试的数据清理
- Mock依赖方
- 彻底排除第三方环境对单元测试的影响
Unitils提供的特性和Spring的一些使用技巧能够帮助我们解决以上问题。下面以实战的形式来具体分解:
断言JavaBean或集合类
Unitils提供了ReflectionAssert用于对objects/collections的断言,可以断言两个对象的属性是否全部相等,例如:
//Bean断言,比较对象中每个属性的值
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
ReflectionAssert.assertReflectionEquals(user1, user2);
也可以断言集合中的成员是否全部相等,例如:
//集合对象断言,比较集合中每个元素的值
List<Double>myList = new ArrayList<Double>();
myList.add(1.0);
myList.add(2.0);
ReflectionAssert.assertReflectionEquals(Arrays.asList(1,2), myList);
当然,ReflectionAssert还有其他的一些特性,比如忽略一些空值、断言对象某些属性等等,有需要的同学可以继续深究
依赖DB数据
我们单元测试中往往会涉及到DAO的操作,需要DB中存在所依赖的数据,如果通过原生态的jdbc去初始化就非常繁琐,Unitils集成了DBUnit,通过xml的方式来做数据初始化,具体实战步骤如下:
1)在unitils.properties中配置数据源
例如:
database.driverClassName=oracle.jdbc.driver.OracleDriver
database.url=jdbc:oracle:thin:@10.20.147.2:1521:TESTDB
database.userName=money
database.password=ali88
2)编写初始化数据的XML
按照预定格式编写数据文件,例如:
<dataset>
<billing_log_monitor
GUID="wb_xinmin.zhao_test121"
classname="ChargeByTimesTask"
methodname="charge"
type="DAY_CHARGE_THREAD_END"
recorddate="2009-12-06"
description="chargeByTimeTase,121,151"
error_msg="12"
isurgent="0"
/>
</dataset>
3)单元测试中应用
应用这个特性非常简单,只需要通过@DataSet载入上面编写好的XML即可,这个注解可以加在类名上(这个类所有测试方法都会加载XML中的数据),也可以加在方法名上(这作用于这个方法),例如:
@Test
@DataSet("LogDAOTest.xml")
publicvoid testGetLogs()
数据的载入模式是可以定制的,Unitils提供了以下几种:
CleanInsertLoadStrategy:先把dataSet中涉及到的表都清掉,再插入dataset中的数据
InsertLoadStrategy:直接插入dataset中的数据
RefreshLoadStrategy:DB已存在的数据就修改,不存在的就插入
UpdateLoadStrategy:直接通过dataset中的数据修改DB中数据,如果不存在就抛出错误
一般说来使用RefreshLoadStrategy比较合理,配置方法就是在unitils.properties中配置:
DbUnitModule.DataSet.loadStrategy.default=org.unitils.dbunit.datasetloadstrategy.impl.RefreshLoadStrategy
单元测试的数据清理
单元测试过后往往会留下很多垃圾数据,并且有可能会导致下次跑单元测试失败(比如唯一性属性等问题),就有清理单元测试数据的需求,unitils提供了事务的方式来满足这个需求,也就是在单元测试过后不提交事务。配置事务的方式有两种,一种是全局化配置(unitils.properties),如:
DatabaseModule.Transactional.value.default=disabled|commit|rollback
其中disabled是没有事务,commit是单元测试方法过后提交事务,rollback是回滚事务。另外也可以直接在方法上加@Transactional来更细粒度的控制,如:
@Transactional(TransactionMode.ROLLBACK)
publicvoid testGetLogsCount()
以上表示testGetLogsCount执行过后自动回滚事务
Mock依赖方
Mock的方式很多,unitils集成了easyMock也提供了mock的功能,并且使用起来更加方便,比如要想对logService注入一个mock出来的logDAO,只需:
private ILogService logService;
@InjectInto(target = "logService", property = "logDAO")
然后具体调用过程中:
logDAOMock.onceReturns(5).getLogsCount(null);
Assert.isTrue(new Integer(5).equals(logService.getLogsCount(null)));
即可完成mock的操作,方式有很多,重在方便
彻底排除第三方环境对单元测试的影响
单元测试依赖第三方常常因为环境问题导致单元测试失败,当然如果能在所以单元测试中都mock所依赖的类是可以解决这个问题,但在大型项目中很难保证没有漏网之鱼,特别是存在多层依赖的情况。其实通过Spring配置并在测试目录下替换掉对第三方的依赖就可以从根本上掐断祸端。具体实施中,我们在写spring bean配置文件的时候,可以把对第三方依赖的接口放在一个文件中,在测试目录下我们用另外一个配置文件替换它,此文件配置的都是我们mock第三方接口的实现类,这样单元测试的时候只要是通过spring依赖的,都会自动去依赖我们的mock,无需在单元测试代码里再去mock。当然,如果需要mock出不同行为,那就需要在我们的mock类里去细化,可以通过线程变量来传递信息。这种方法没有使用任何mock框架,原理简单,关键是来得彻底。