在具体做单元测试的过程中,会遇到一些小问题,比如:
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" )
public void 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 )
public void 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 框架,原理简单,关键是来得彻底。