本文主要为对dbunit getting started 简单理解,其他参考文章见文末。
dbunit(官网:http://www.dbunit.org/)是一种扩展于JUnit的数据库驱动测试框架,它使数据库在测试过程之间处于一种已知状态,如果一个测试用例对数据库造成了破坏性影响,它可以帮助避免造成后面的测试失败或者给出错误结果。这是官方的解释,个人理解dbunit的好处是如果一个测试用例影响了数据库,或者说数据库被发生了变动,dbunit可以很容易地发现问题,并使其他测试用例不受影响,也就是一种已知并可控的状态。
dbunit属于极限编程(Extreme Programming)范畴,推荐测试优先的开发和持续集成。个人觉得dbunit最大的好处就在这“持续集成”上。以前只要数据库发生变化,比如改了表结构,哪怕仅仅添加一个字段,后续工作也不少,已经写完的相关DAO必须得改,测试用例也要改,改了数据库忘记改代码的情况也不少见。这还是对一个人来说,如果是多人合作,你很大可能会碰到这种情况:boss指着屏幕质问你:为什么10分钟前测试通过的功能,现在连界面都看不到...于是你装无辜,装可怜,查日志,查代码,最后只能无力地怒吼:谁动了我的数据.....很多时候单元测试只不过是一种摆设,少有项目完成后还可以完整运行的测试方法,如果你能迅速地跑起测试用例,问题自然暴露,让我们来看看dbunit如何来解决这些问题。
2.DbUnit原理
dbunit通过维护真实数据库与数据集(DataSet)之间的关系来发现与暴露测试过程中的问题。此处DataSet可以自建,可以由数据库导出,并以多种方式体现,xml文件、XLS文件和数据库查询数据等,一般多用XML文件。在测试过程中,DataSet被称为期望结果(expected result),真实数据库被称真实结果(actual result),你所要做的就是通过dbunit完成期望结果与真实结果之间的操作与比较,从而发现问题和校验结果。
dbUnit包括三个核心部分(详见:http://www.dbunit.org/components.html#cleanInsert):
1)IDatabaseConnection :描述dbunit数据库连接接口;
2)IDataSet:数据集操作接口;
3)DatabaseOperation:描述测试用例测试方法执行前与执行后所做操作的抽象类;
值得关注的是 DatabaseOperation的各种实现,比较常用的有 REFRESH、DELETE_ALL和CLEAN_INSERT等。这些操作关系到数据集与数据库数据的同步、数据准备,不小心就会对数据库原有数据造成影响,所以务必做好备份。
DatabaseOperation.REFRESH:同步DataSet数据到目标数据库,DataSet中有database中也有的数据,会从DataSet更新到database,DataSet中有database中没有的数据会插入到database中。对database中有DataSet中没有的数据不造成影响,适用于database中有其他数据的情况;
DatabaseOperation.DELETE_ALL:删除database中DataSet有的所有表数据,DataSet中没有的表不造成影响。
DatabaseOperation.CLEAN_INSERT:先进行DELETE_ALL操作,在把DataSet中有database中没有的数据插入database;
3.DbUnit应用流程
1) 准备DataSet;
即准备测试数据(expected result),可以手写,也可以直接从数据库中导出,以XML文件为列,导出代码如下:
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost/ecshop", "root", "");
IDatabaseConnection connection = new DatabaseConnection(conn);
QueryDataSet dataSet = new QueryDataSet(connection);
//将整个ecs_card表里的数据导出到 xml文件里
dataSet.addTable("ecs_card");
FlatXmlDataSet.write(dataSet, new FileOutputStream("ecs_card.xml"));
//导出数据库所有表
//IDataSet dataSet= connection.createDataSet();
// FlatXmlDataSet.write(dataSet, new FileOutputStream("allTable.xml"));
}
2)继承dbunit的测试基类DBTestCase ,建立数据库与数据集的连接;
dbunit封装了四种数据库连接配置方式:PropertiesBasedJdbcDatabaseTester、JdbcBasedDBTestCase、JndiBasedDBTestCase和DataSourceBasedDBTestCase,默认使用PropertiesBasedJdbcDatabaseTester。最好在你的测试类构造方法中配置。DataSet加载以重载基类 getDataSet()方法实现,代码如下:
public class SimpleTest extends DBTestCase
{
public SimpleTest(String name)
{
super( name );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "com.mysql.jdbc.Driver" );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, "jdbc:mysql://localhost/ecshop?useUnicode=true&characterEncoding=GBK" );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "root" );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "" );
// System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_SCHEMA, "" );
}
@Override
protected IDataSet getDataSet() throws Exception
{
return new FlatXmlDataSetBuilder().build(new FileInputStream("ecs_card.xml"));
}
}
public class SimpleTest extends DBTestCase
{
...
@Override
protected DatabaseOperation getSetUpOperation() throws Exception {
logger.info("setup REFRESH operation....");
return DatabaseOperation.REFRESH; //刷新dataset 数据到 database
// return DatabaseOperation.REFRESH; //
}
@Override
protected DatabaseOperation getTearDownOperation() throws Exception {
logger.info("teardown NONE operation....");
return DatabaseOperation.NONE; //do nothing
}
...
}
4) 编写测试方法testXXX(),完整测试用例如下:
public class SimpleTest extends DBTestCase{
private static Log logger = LogFactory.getLog(SimpleTest.class);
public SimpleTest() {
}
public SimpleTest(String name) {
super(name);
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "com.mysql.jdbc.Driver" );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, "jdbc:mysql://localhost/ecshop?useUnicode=true&characterEncoding=GBK" );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "root" );
System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "" );
// System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_SCHEMA, "" );
}
@Override
protected IDataSet getDataSet() throws Exception {
logger.info("load xml data....");
return new FlatXmlDataSetBuilder().build(new FileInputStream("ecs_card.xml")); //To change body of implemented methods use File | Settings | File Templates.
}
@Override
protected DatabaseOperation getSetUpOperation() throws Exception {
logger.info("setup clean_insert operation....");
return DatabaseOperation.REFRESH; //
// return DatabaseOperation.REFRESH; //
}
@Override
protected DatabaseOperation getTearDownOperation() throws Exception {
logger.info("teardown NONE operation....");
return DatabaseOperation.NONE; //do nothing
}
/**
* 数据库与备份库比较
* @throws Exception
*/
public void testdb() throws Exception{
//取数据库表
IDataSet dataSet = getConnection().createDataSet();
ITable dbTable = dataSet.getTable("ecs_card");
//取XML备份表
ITable xmlTable = getXmlTable("ecs_card","ecs_card");
Assertion.assertEquals(xmlTable,dbTable);
}
/**
* 测试表结构
* @throws Exception
*/
public void testDbStructure() throws Exception {
//取数据库表
IDataSet dataSet = getConnection().createDataSet();
ITable dbTable = dataSet.getTable("ecs_card");
//取XML备份表
ITable xmlTable = getXmlTable("ecs_card1","ecs_card");
dbTable = DefaultColumnFilter.includedColumnsTable(dbTable,xmlTable.getTableMetaData().getColumns());
Assertion.assertEquals(xmlTable,dbTable);
}
/**
* 测试插入一条记录
* @throws Exception
*/
public void testInsertRecord() throws Exception {
/*这里dao 插入一条记录,省略*/
IDataSet dataSet = getConnection().createDataSet();
ITable dbTable = dataSet.getTable("ecs_card");
//取XML期望表
ITable xmlTable = getXmlTable("ecs_card_exp","ecs_card");
Assertion.assertEquals(xmlTable,dbTable);
}
public void testQueryRecord() throws Exception {
/*这里dao 查询,省略*/
String sql = "SELECT *FROM ecs_card ";
ITable dbTable = (ITable) getConnection().createQueryTable("ecs_card",sql);
assertEquals(1,dbTable.getRowCount());
String cardName = (String) dbTable.getValue(0,"card_name");
assertEquals("贺卡",cardName);
}
/**
* 获取XML TABLE,默认取当前目录
* @param xmlFileName
* @param tableName
* @return
* @throws Exception
*/
private ITable getXmlTable(String xmlFileName,String tableName) throws Exception {
//取XML备份表
FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
builder.setColumnSensing(true);
IDataSet xmlDataSet = builder.build(new FileInputStream(xmlFileName+".xml"));
return xmlDataSet.getTable(tableName);
}
}
4. 依赖包