一:Dao层的单元测试实践Dao单元测试的问题
选择了Unitils来集成Spring、Dbunit等,完成Dao层的单元测试工作,结合maven使用。
Dao的代码很简单,是查询、更新一个用户的账户信息
public class AccountDao extends JdbcDaoSupport {
public Account getAccount(String accountId) {
List<Account> list = null;
list = getJdbcTemplate().query("select account_id,balance from tb_account where account_id=?",
new Object[] { accountId }, new RowMapper<Account>() {
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account acc = new Account();
acc.setAccountId(rs.getString("account_id"));
acc.setBalance(rs.getInt("balance"));
return acc;
}
});
if (list.size() > 0) {
return list.get(0);
} else {
return null;
}
}
public int updateAccount(String accountId, int balance) {
int ret = getJdbcTemplate().update("update tb_account set balance = ? where account_id =?",
new Object[] { balance, accountId });
return ret;
}
}
一、Maven的POM文件修改
在Dao工程中的POM文件加入如下
<dependency>
<groupId>org.unitils</groupId>
<artifactId>unitils-dbunit</artifactId>
<version>${unitils.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.unitils</groupId>
<artifactId>unitils-spring</artifactId>
<version>${unitils.version}</version>
<scope>test</scope>
</dependency>
unitils.version目前最新的为3.3版本
二、Unitils的环境配置
Unitils的启动,需要一个配置文件unitils.properties,这个文件默认需要放到classpath下,我们一般为test/resources/unitils.properties文件。文件内容如下
database.driverClassName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/test
database.userName=root
database.password=root
database.schemaNames=test
database.dialect=mysql
DatabaseModule.Transactional.value.default=rollback
database.driverClassName为测试数据库的Jdbc驱动
database.url为测试数据库的连接串
database.userName为测试数据库用户名
database.schemaNames为测试数据库的schema,mysql可以不需要,Oracle必填。
database.dialect填写为数据库类型,取值有mysql,oracle,derby等
DatabaseModule.Transactional.value.default指的是单元测试对数据库的修改的事务策略,有rollback,disable,commit等选择,我们一般选择回滚 rollback
三、Spring的集成
Unitils提供了Spring的集成功能 - 将Dao依赖的Spring配置,包括Property解析、DataSource、事务管理等主要是一些基础配置放到Maven工程的test/resources/testapplication/appContext-common.xml中。
<bean
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreResourceNotFound" value="false" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="locations">
<list>
<value>classpath:testapplication/config.properties</value>
</list>
</property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>${jdbc.driverClassName}</value>
</property>
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
<property name="maxActive">
<value>${jdbc.maxActive}</value>
</property>
<property name="maxIdle">
<value>${jdbc.maxIdle}</value>
</property>
<property name="initialSize">
<value>${jdbc.maxIdle}</value>
</property>
<property name="maxWait">
<value>18000</value>
</property>
<property name="defaultAutoCommit">
<value>false</value>
</property>
</bean>
<!-- 事务管理器配置,单数据源事务 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
之所以单独把单元测试的基础Spring配置抽出来是因为这里注入的数据源和事务管理器,都用最简单的单数据库事。避免实际开发中多数据源事务的问题影响结果。 - 使用Unitils的Spring启动替换功能,将Spring中的正常的DataSource换为Unitils自身的DataSource。这样做的好处是数据准备的操作和业务sql在一个事务中进行,可以方便一起回滚,不对数据库造成影响。替换的DataSource也是一个Spring配置文件,放到test/resources/testapplication/testDataSource.xml中。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
_http://www.springframework.org/schema/beans/spring-beans-3.1.xsd_
_http://www.springframework.org/schema/context_
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<bean id="dataSource" class="org.unitils.database.UnitilsDataSourceFactoryBean" />
</beans>
三、测试数据的准备
可以根据dbunit的xml格式准备测试数据,通过执行ExportData这个对象来导出现有测试库的数据,在命令行里面输入要导出的表名,即可把当前测试数据库的现有数据导出为xml
测试的xml文件默认放到test/resources下和测试的代码相同的package中,比如test/resources/com/xxx/dao/下。
四、单元测试用例的编写
单元测试用例需要继承UnitilsJUnit3这个基类,顾名思义这个测试套件是依赖Junit3的。Unitils另外也提供了UnitilsJUnit4的基类。下面是我们的一个测试样例代码
public class AccountDaoTest extends UnitilsJUnit3 {
@SpringApplicationContext({ "classpath:testapplication/appContext-common.xml",
"classpath:testapplication/testDatasource.xml", "classpath:META-INF/spring/applicationContext-*.xml" })
protected ApplicationContext applicationContext;
@SpringBeanByType
private AccountDao accountDao;
@DataSet("ACCOUNT.xml")
public void testGetAccount() {
Account account = accountDao.getAccount("S31993k");
System.out.println(JSON.toJSON(account));
assertEquals(100, account.getBalance());
}
@DataSet("ACCOUNT.xml")
public void testGetAccountNull() {
Account account = accountDao.getAccount("23");
assertEquals(null, account);
}
@DataSet("ACCOUNT.xml")
@ExpectedDataSet("ACCOUNT_NEW.xml")
public void testUpdateAccount() {
accountDao.updateAccount("S31993k", 35);
}
}
protected ApplicationContext applicationContext;
是Spring上下文加载后的变量。上面的@ SpringApplicationContext是指明要加载的Spring配置文件到一个变量,可以通过一个String数组和通配符加载多个配置,可以看到这里我们把common和测试数据源的testDatasource.xml都加载了。如果一个工程中,可以抽象出一个公用的测试基类,将Spring的上下文保存在基类中。
private AccountDao accountDao;
是我们要测试的目标对象,这里需要在前面加上@SpringBeanByType的标注,这样Unitils会自动根据类型,将目标对象从Spring上下文取出,注入到测试代码中
public void testUpdateAccount ()
是测试插入的方法,其上的 @DataSet("ACCOUNT.xml")
指的是插入前的预制数据,Unitils和Dbunit会在执行该方法前,将EMPTY_TABLE.xml中的数据导入到数据库中,下面给出的是一个样例数据的xml。执行前,Unitils会清空该表,然后插入指定的测试数据。注:由于事务最后回滚,清空的动作不会提交,所以不用担心数据的损失。
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<tb_account account_id="S31993k" balance="100"/>
</dataset>
@ExpectedDataSet("ACCOUNT_NEW.xml")
为执行测试用例后的期望数据,Unitils会比较实际结果和期望值,看看是否一致。如果不一致则抛出测试失败。ACCOUNT_NEW.xml的内容如下
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<tb_account account_id="S31993k" balance="35"/>
</dataset>
public void testGetAccount()是查询一个已经存在的帐户。最后通过断言查询是否存在编号为"S31993k"。注:每次执行一个用例方法的时候,DBUnit都会重新初始化相关的数据表,所以不用担心前面的测试用例操作会影响当前的用例结果
public void testGetAccountNull
这是一个尝试查询一个不存在的帐户信息,最后Dao应该返回的是Null,这里用断言做了判断。
|