在做 Java 企业程序的时候,不可避免地要和外部资源打交道,比如数据库, Http 请求等。对于这些外部资源的处理,我们可采取的操作或者是直接处理或者是模拟处理。当我们使用 Webwork , Spring , Hibernate 等框架时,我们要测试的并不仅仅是 Java 代码,我们还要测试依赖于这些框架的配置文件等等。因此,对于数据持久化的测试, Mock 方法是行不通的,我们需要真实地测试数据库操作。对于持久化测试来说,重要的是创造出已知的“干净的”的准备数据。如果我们在测试一个持久化方法前不能确定数据库到底存着什么数据,我们只能通过反复地查看数据库数据来验证测试方法的正确性了(这就是我和大多数人以前使用的最“直接”的方法)。现在就让我们使用 DbUnit ,来更好的更自动化的测试持久化操作吧!
先介绍一下 DbUnit 。 DbUnit 是一个 JUnit 扩展,适用于数据驱动的程序。使用 DbUnit ,可以在测试运行期间将数据库的数据处于已知状态,这样在测试时可以方便地写出测试断言,也能自动地完成对数据持久化方法的测试。在使用上, DbUnit 也很简单, 它提供了大量的类对与数据库相关的操作进行了抽象和封装,大多数情况下你只需要使用少量简单的 API 。
下面我通过一个实际的小例子,介绍一下如何使用 DbUnit 。我也是刚刚使用上了 DbUnit ,经验上不是很丰富,如果文中有不对的地方,也欢迎指正。这个例子很简单,我将较为详细地说明如何使用 Hibernate 和 DbUnit 进行测试。
测试第一步,准备数据集。操作的数据表就是如下的 Account 表(使用的数据库为Mysql):
create table Account(
id bigint not null auto_increment,
name varchar ( 50 ) not null ,
primary key (id)
) character set gbk;
至于 Account 类,映射文件等这里就不给出了。AccountDAO 接口很简单,只有两个方法:
public interface AccountDAO {
void insert(Account a);
List < Account > findAll();
}
实现类如下:
public class AccountHibernateDAO implements AccountDAO{
public void insert(Account a){
Session s = HibernateSessionFactory.getSession();
s.save(a);
s.close();
}
public List < Account > findAll(){
Session s = HibernateSessionFactory.getSession();
List < Account > l = (List < Account > )s.createCriteria(Account. class ).list();
s.close();
return l;
}
}
在测试前,要准备出数据表中要装入的数据(也就是数据集),这里给出与Account表对应的数据集文件 Accout.xml 内容如下:
<? xml version='1.0' encoding='UTF-8' ?>
< dataset >
< Account id ="1" name ="kafka" />
< Account id ="2" name ="0102" />
</ dataset >
数据集就是一个 xml 文件, <dataset> 中的每个节点对应的就是一条表数据记录(一个 dataset文件可以对应多个数据表记录 )。这里的 <Account> 节点对应的就是 Account 表,属性就是表中的字段,属性值就是字段值了。在做测试时,数据集中的内容可以手动敲进去,也可以通过工具将数据库中的数据导出来。 对于数据集的详细信息,可参考 http://dbunit.sourceforge.net/components.html#FlatXmlDataSet 。
测试第二步,扩展 DBTestCase 。 DBTestCase 是继承自 JUnit 的类,扩展它需要实现 getDataSet() 来提供数据集。另外,你也可以根据需要扩展继承于 DBTestCase 的子类 JdbcBasedDBTestCase , DataSourceBasedDBTestCas , JndiBasedDBTestCase 。下面是继承于 DBTestCase 的 AccountHibernateDAO 的测试类 AccountHibernateDAOTest :
package hibernatesample.dao.impl;
import hibernatesample.domain.Account;
import hibernatesample.util.HibernateSessionFactory;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import org.dbunit.Assertion;
import org.dbunit.DBTestCase;
import org.dbunit.PropertiesBasedJdbcDatabaseTester;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.operation.DatabaseOperation;
public class AccountHibernateDAOTest extends DBTestCase {
private AccountHibernateDAO accountDAO;
public AccountHibernateDAOTest(){
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, HibernateSessionFactory.getDriverClass());
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, HibernateSessionFactory.getConnectionURL());
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME,HibernateSessionFactory.getUsername());
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD,HibernateSessionFactory.getPassword());
}
@Override
protected IDataSet getDataSet() throws Exception {
String path = " hibernatesample " + File.separator + " dao " + File.separator + " dataset " + File.separator + " Account.xml " ;
InputStream in = this .getClass().getClassLoader().getResourceAsStream(path);
return new FlatXmlDataSet(in);
}
@Override
protected DatabaseOperation getSetUpOperation() throws Exception {
return DatabaseOperation.CLEAN_INSERT;
}
@Override
protected DatabaseOperation getTearDownOperation() throws Exception {
return DatabaseOperation.NONE;
}
protected void setUp() throws Exception {
super .setUp();
accountDAO = new AccountHibernateDAO();
}
public void testInsert() {
Account a = new Account();
a.setName( " aa " );
accountDAO.insert(a);
List < Account > l = accountDAO.findAll();
assertEquals( 3 , l.size());
Account b = l.get( 2 );
assertEquals( " aa " , b.getName());
}
public void testFindAll() {
List < Account > l = accountDAO.findAll();
assertEquals( 2 , l.size());
Account a = l.get( 0 );
assertEquals( new Long( 1 ), a.getId());
assertEquals( " kafka " , a.getName());
Account b = l.get( 1 );
assertEquals( new Long( 2 ), b.getId());
assertEquals( " 0102 " , b.getName());
}
public void testDataset() throws Exception {
IDataSet databaseDataSet = getConnection().createDataSet();
ITable actualTable = databaseDataSet.getTable( " Account " );
IDataSet expectedDataSet = getDataSet();
ITable expectedTable = expectedDataSet.getTable( " Account " );
Assertion.assertEquals(expectedTable, actualTable);
}
}
上面的DBTestCase 依赖于 IDatabaseTester 接口完成工作,而 PropertiesBasedJdbcDatabaseTester 就是其使用的默认实现, AccountHibernateDAOTest 构造函数的作用是完成数据库连接参数的设置。 protected IDataSet getDataSet() 实现了装载数据集到 IDataSet 中。 getSetUpOperation 和 getTearDownOperation 是可选的方法,返回的 DatabaseOperation 为 DBTestCase 在 SetUp 和 TearDown 中将执行的操作, getSetUpOperation 默认的操作为 DatabaseOperation.CLEAN_INSERT ,也就是先清空数据表中的数据再插入数据集中的数据到数据表中。getTearDownOperation 默认的操作为 DatabaseOperation.NONE ,就是什么也不处理。可选的操作还有几个,可参考文档进行设置,但默认的设置是最通用的了。 testDataset ()只是测试数据集中的数据和装载到数据库中数据是否一致。
通过上面的设置,我们就可以测试dao方法的正确性了,而我们要做的只是准备dataset文件及使用少量的DbUnit API(可以将这些操作写到一个抽象类中,测试类都继承自这个抽象类)。
先介绍一下 DbUnit 。 DbUnit 是一个 JUnit 扩展,适用于数据驱动的程序。使用 DbUnit ,可以在测试运行期间将数据库的数据处于已知状态,这样在测试时可以方便地写出测试断言,也能自动地完成对数据持久化方法的测试。在使用上, DbUnit 也很简单, 它提供了大量的类对与数据库相关的操作进行了抽象和封装,大多数情况下你只需要使用少量简单的 API 。
下面我通过一个实际的小例子,介绍一下如何使用 DbUnit 。我也是刚刚使用上了 DbUnit ,经验上不是很丰富,如果文中有不对的地方,也欢迎指正。这个例子很简单,我将较为详细地说明如何使用 Hibernate 和 DbUnit 进行测试。
测试第一步,准备数据集。操作的数据表就是如下的 Account 表(使用的数据库为Mysql):
create table Account(
id bigint not null auto_increment,
name varchar ( 50 ) not null ,
primary key (id)
) character set gbk;
至于 Account 类,映射文件等这里就不给出了。AccountDAO 接口很简单,只有两个方法:
public interface AccountDAO {
void insert(Account a);
List < Account > findAll();
}
实现类如下:
public class AccountHibernateDAO implements AccountDAO{
public void insert(Account a){
Session s = HibernateSessionFactory.getSession();
s.save(a);
s.close();
}
public List < Account > findAll(){
Session s = HibernateSessionFactory.getSession();
List < Account > l = (List < Account > )s.createCriteria(Account. class ).list();
s.close();
return l;
}
}
在测试前,要准备出数据表中要装入的数据(也就是数据集),这里给出与Account表对应的数据集文件 Accout.xml 内容如下:
<? xml version='1.0' encoding='UTF-8' ?>
< dataset >
< Account id ="1" name ="kafka" />
< Account id ="2" name ="0102" />
</ dataset >
数据集就是一个 xml 文件, <dataset> 中的每个节点对应的就是一条表数据记录(一个 dataset文件可以对应多个数据表记录 )。这里的 <Account> 节点对应的就是 Account 表,属性就是表中的字段,属性值就是字段值了。在做测试时,数据集中的内容可以手动敲进去,也可以通过工具将数据库中的数据导出来。 对于数据集的详细信息,可参考 http://dbunit.sourceforge.net/components.html#FlatXmlDataSet 。
测试第二步,扩展 DBTestCase 。 DBTestCase 是继承自 JUnit 的类,扩展它需要实现 getDataSet() 来提供数据集。另外,你也可以根据需要扩展继承于 DBTestCase 的子类 JdbcBasedDBTestCase , DataSourceBasedDBTestCas , JndiBasedDBTestCase 。下面是继承于 DBTestCase 的 AccountHibernateDAO 的测试类 AccountHibernateDAOTest :
package hibernatesample.dao.impl;
import hibernatesample.domain.Account;
import hibernatesample.util.HibernateSessionFactory;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import org.dbunit.Assertion;
import org.dbunit.DBTestCase;
import org.dbunit.PropertiesBasedJdbcDatabaseTester;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.operation.DatabaseOperation;
public class AccountHibernateDAOTest extends DBTestCase {
private AccountHibernateDAO accountDAO;
public AccountHibernateDAOTest(){
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, HibernateSessionFactory.getDriverClass());
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, HibernateSessionFactory.getConnectionURL());
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME,HibernateSessionFactory.getUsername());
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD,HibernateSessionFactory.getPassword());
}
@Override
protected IDataSet getDataSet() throws Exception {
String path = " hibernatesample " + File.separator + " dao " + File.separator + " dataset " + File.separator + " Account.xml " ;
InputStream in = this .getClass().getClassLoader().getResourceAsStream(path);
return new FlatXmlDataSet(in);
}
@Override
protected DatabaseOperation getSetUpOperation() throws Exception {
return DatabaseOperation.CLEAN_INSERT;
}
@Override
protected DatabaseOperation getTearDownOperation() throws Exception {
return DatabaseOperation.NONE;
}
protected void setUp() throws Exception {
super .setUp();
accountDAO = new AccountHibernateDAO();
}
public void testInsert() {
Account a = new Account();
a.setName( " aa " );
accountDAO.insert(a);
List < Account > l = accountDAO.findAll();
assertEquals( 3 , l.size());
Account b = l.get( 2 );
assertEquals( " aa " , b.getName());
}
public void testFindAll() {
List < Account > l = accountDAO.findAll();
assertEquals( 2 , l.size());
Account a = l.get( 0 );
assertEquals( new Long( 1 ), a.getId());
assertEquals( " kafka " , a.getName());
Account b = l.get( 1 );
assertEquals( new Long( 2 ), b.getId());
assertEquals( " 0102 " , b.getName());
}
public void testDataset() throws Exception {
IDataSet databaseDataSet = getConnection().createDataSet();
ITable actualTable = databaseDataSet.getTable( " Account " );
IDataSet expectedDataSet = getDataSet();
ITable expectedTable = expectedDataSet.getTable( " Account " );
Assertion.assertEquals(expectedTable, actualTable);
}
}
上面的DBTestCase 依赖于 IDatabaseTester 接口完成工作,而 PropertiesBasedJdbcDatabaseTester 就是其使用的默认实现, AccountHibernateDAOTest 构造函数的作用是完成数据库连接参数的设置。 protected IDataSet getDataSet() 实现了装载数据集到 IDataSet 中。 getSetUpOperation 和 getTearDownOperation 是可选的方法,返回的 DatabaseOperation 为 DBTestCase 在 SetUp 和 TearDown 中将执行的操作, getSetUpOperation 默认的操作为 DatabaseOperation.CLEAN_INSERT ,也就是先清空数据表中的数据再插入数据集中的数据到数据表中。getTearDownOperation 默认的操作为 DatabaseOperation.NONE ,就是什么也不处理。可选的操作还有几个,可参考文档进行设置,但默认的设置是最通用的了。 testDataset ()只是测试数据集中的数据和装载到数据库中数据是否一致。
通过上面的设置,我们就可以测试dao方法的正确性了,而我们要做的只是准备dataset文件及使用少量的DbUnit API(可以将这些操作写到一个抽象类中,测试类都继承自这个抽象类)。