1. 配置
Maven工程文件pom.xml
工程环境使用Maven进行构建,持久化层使用mybatis实现domain vo到数据库的流入。使用unitils框架的第一步得导入相应的jar包。
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.unitils</groupId>
<artifactId>unitils</artifactId>
<version>2.4</version>
</dependency>
注: 只需要导入unitils包和dbunit包即可,unitils本身还包括一些依赖包:unitils-dbunit,unitils-database,unitils-spring等等。这些依赖包最好不要导入,导入后会覆盖unitils包下面的类,导致一些意想不到的错误
unitils.propertis
在测试源代码的根目录下新建一个项目级别的配置文件unitils.propertis
unitils.modules=database,dbunit
#unitils.module.dbunit.className=sample.unitils.module.CustomExtModule
#database
#database.driverClassName=org.hsqldb.jdbcDriver
#database.url=jdbc:hsqldb:data/sampledb;shutdown=true
#database.dialect = hsqldb
#database.userName=sa
database.driverClassName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/sampledb_test?useUnicode=true&characterEncoding=UTF-8
database.dialect = mysql
database.userName=root
database.password=123456
database.schemaNames=sampledb_test
#需设置false,否则我们的测试函数只有在执行完函数体后,才将数据插入的数据表表中
unitils.module.database.runAfter=false
# The database maintainer is disabled by default.
updateDataBaseSchema.enabled=true
#This table is by default not created automatically
#数据库表生成策略
dbMaintainer.autoCreateExecutedScriptsTable=true
dbMaintainer.keepRetryingAfterError.enabled=true
#测试数据库脚本目录
dbMaintainer.script.locations=src/test/resources/dbscripts
#配置数据集工厂
DbUnitModule.DataSet.factory.default=com.morecrazy.myspring.test.dataset.excel.XlsDataSetFactory
DbUnitModule.ExpectedDataSet.factory.default=com.morecrazy.myspring.test.dataset.excel.XlsDataSetFactory
#数据集加载策略
DbUnitModule.DataSet.loadStrategy.default=org.unitils.dbunit.datasetloadstrategy.impl.CleanInsertLoadStrategy
DatabaseModule.Transactional.value.default=commit
# XSD generator
dataSetStructureGenerator.xsd.dirName=src/test/resources/xsd
在unitils.propertis中设置了测试要使用的数据库,数据库表的生成策略,脚本目录,数据集工厂和数据集的加载策略。可选的策略有四种:
- CleanInsertLoadStrategy:先删除dataSet中有关数据,然后再插入
- InsertLoadStrategy:只插入数据
- RefreshLoadStrategy:有同样Key的数据更新,没有的插入
- UpdateLoadStrategy:有同样key的数据更新,没有的不做操作
需要特别关注的是数据集工厂的配置。一般情况下作为测试和验证数据的载体是xml和excel两种格式,unitils需要具备访问相应格式的数据集工厂。网上讲了很多怎么根据xml数据集工厂创建一个支持访问excel的数据集工厂。代码复杂。经过验证,我发现直接实现unitils提供的DataSetFactory即可。它提供对xls格式文件的读取
public class XlsDataSetFactory implements DataSetFactory {
private String defaultSchemaName;
public MultiSchemaDataSet createDataSet(File... dataSetFiles) {
if (dataSetFiles == null || dataSetFiles.length <= 0 || dataSetFiles[0] == null) {
throw new RuntimeException("xls");
}
if (!dataSetFiles[0].getName().endsWith(".xls")) {
throw new RuntimeException("xls");
}
MultiSchemaDataSet multiSchemaDataSet = new MultiSchemaDataSet();
IDataSet idataSet = null;
try {
idataSet = new XlsDataSet(dataSetFiles[0]);
} catch (DataSetException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
multiSchemaDataSet.setDataSetForSchema(defaultSchemaName, idataSet);
return multiSchemaDataSet;
}
public String getDataSetFileExtension() {
return "xls";
}
public void init(Properties configuration, String defaultSchemaName) {
this.defaultSchemaName = defaultSchemaName;
}
}
一个类文件就搞定。这也是因为之前包的导入的关系,如果除了导入unitils包之外,再导入它的一些模块包,比如:unitils-dbunit,unitils-spring之类,那么上面这种方法就行不通了。会在格式验证出抛出异常
if (!dataSetFiles[0].getName().endsWith(".xls")) {
throw new RuntimeException("xls");
}
不知道这是一个投机取巧的办法还是unitils的bug,但是用这种方式确实方便很多。我没有试过高版本的unitils的DataSetFactory支不支持xls,如果发现不支持,那么还是老老实实的构造一个支持xls的数据集工厂。至于怎么弄,我会在下一篇文章中分析,此处篇幅有限,就使用这种省事但效果一样的办法。
2. 测试代码
配置完成后,就可以针对相应的dao进行测试。此处我采用mybatis作为持久层的实现。mybatis相对hibernate更灵活,能自己控制sql语句,并且层次清晰,掌握起来容易。只需要写好相应的dao接口,配置好映射文件即可。
关于unitils测试hibernate持久层的方法,网上很多,也比较全。unitils也很好的支持hibernate。有相应的模块。上网找了一遍,并没有发现unitils支持mybatis的框架,不过没有关系,用自己的方法,取其好的地方,达到目的即可。
先贴一段测试代码
public class UserDaoTest extends BaseDaoTest{
private static ApplicationContext ctx;
private UserDao userDao;
@BeforeClass
public static void setUpBeforeClass() {
ctx = new ClassPathXmlApplicationContext("classpath:applicationContext-mybatis.xml");
}
@Before
public void setUp() {
userDao = (UserDao) ctx.getBean("userDao");
}
@Test
@DataSet("Users.xls")//准备数据
public void findUserByUserName() {
if (userDao == null) { System.out.println("ok");}
User user = userDao.getUserByUserName("jhon");
assertNull("不存在用户名为tony的用户!", user);
user = userDao.getUserByUserName("jan");
assertNotNull(user);
assertEquals("jan",user.getUserName());
}
// 验证数据库保存的正确性
@Test
@ExpectedDataSet("ExpectedSaveUser.xls")// 准备验证数据
public void saveUser()throws Exception {
/**
硬编码创建测试实体
User u = new User();
u.setUserId(1);
u.setUserName("tom");
u.setPassword("123456");
u.setLastVisit(getDate("2011-06-06 08:00:00","yyyy-MM-dd HH:mm:ss"));
u.setCredits(30);
u.setLastIp("127.0.0.1");
**/
//通过XlsDataSetBeanFactory数据集绑定工厂创建测试实体
User u = XlsDataSetBeanFactory.createBean(UserDaoTest.class,"BaobaoTao.SaveUser.xls", "t_user", User.class);
userDao.save(u); //执行用户信息更新操作
}
//验证数据库保存的正确性
@Test
@ExpectedDataSet("ExpectedSaveUsers.xls")// 准备验证数据
public void saveUsers()throws Exception {
List<User> users = XlsDataSetBeanFactory.createBeans(UserDaoTest.class,"BaobaoTao.SaveUsers.xls", "t_user", User.class);
for(User u:users){
userDao.save(u);
}
}
}
父类:BaseDaoTest
@RunWith(UnitilsJUnit4TestClassRunner.class)
@DataSet("BaseDao.xls")
public class BaseDaoTest {
}
unitils整合了junit框架,加上@RunWith(UnitilsJUnit4TestClassRunner.class)注解后即可使用unitils整合junit后的相关功能,比如@Test,@Before,@DataSet等注解的使用。
@DataSet:加载相应的数据集文件,这里使用xls格式:
![加载数据集] (/assets/images/2014/unitils-mybatis-01.png)
excel文件中的每一列和数据库中的表相对应,下面sheet单的名字对应了数据库里相应的table
根据配置的数据加载策略,每次@Test的时候,都是先clean掉数据库里的数据,然后再将excel中的数据插入。
@ExpectedDataSet:验证数据,不会加载到数据库里。只要满足期望的数据在数据库里,测试用例就能通过,而并不需要满足数据里的数据都能在期望excel表中查到。这点是需要注意的。
![加载数据集] (/assets/images/2014/unitils-mybatis-03.png)
注意:使用@ExpectedDataSet注解进行数据验证时,并不会使用配置的数据加载策略,比如CleanInserty之类。所以对于有主键的数据插入,很容易出现冲突,解决办法有两个:
- 进行数据的验证时,不插入带有主键的数据。
-
使用注解的方式强制每次操作时,都清空相应的表,比如:
}@DataSet("BaseDao.xls") public class BaseDaoTest {
BaseDao.xls的作用就是清空数据,里面除了保护table名和column,没有数据
![清空数据] (/assets/images/2014/unitils-mybatis-04.png)
此处为了使用useDao这个spring bean,采用的是以代码的方式,加载配置文件applicationContext-mybatis.xml获取springContext中的bean。
<context:component-scan base-package="com.morecrazy.myspring.dao.mybatis" />
<context:component-scan base-package="com.morecrazy.myspring.service" />
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 使用unitils提供的数据源 -->
<bean id="dataSource" class="org.unitils.database.UnitilsDataSourceFactoryBean"/>
<!-- <bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}" /> -->
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean"
p:dataSource-ref="dataSource"
p:configLocation="classpath:myBatisConfig.xml"
p:mapperLocations="classpath:com/morecrazy/myspring/domain/mybatis/*.xml"/>
<constructor-arg ref="sqlSessionFactory"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
p:sqlSessionFactoryBeanName="sqlSessionFactory"
p:basePackage="com.morecrazy.myspring.dao.mybatis"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<tx:annotation-driven transaction-manager="transactionManager"/>
配置文件里,修改了一处:dataSource,使用unitils配置文件中的数据源。但这样做其实不太好,因为为了不影响到实际的数据库,我们必须得在test资源目录下新建一个mybatis的配置文件。其实这也是没有办法的办法。如果不以代码的方式载入配置文件,而使用通用的注解方式,是不能能获取到持久层的bean,会报null exception。
@RunWith(UnitilsJUnit4TestClassRunner.class)
@SpringApplicationContext({"classpath:applicationContext-mybatis.xml"})
public class UserDaoTest {
private static ApplicationContext ctx;
@SpringBean("userDao")
private UserDao userDao;
}
此处的userDao是获取不到的。但如果持久层是用其他方式实现的,比如Spring Jdbc或者hibernate。这样处理是最好的。所以为了测试mybatis实现的持久化层,暂时只能想到上面用代码硬加载的方式。
最后验证数据没问题了,跑一下程序:
![测试结果] (/assets/images/2014/unitils-mybatis-02.png)