本文主要为对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. 依赖包