DbUnit 简介
1. 引言
在写持久层测试用例的时候,由于我们的测试是要依赖于外部的数据库,数据库里面的数据影响我们测试,往往是要在数据库中去查询得到数据表数据的状态,然后才能在 TestCase 里面写断言。数据表的状态又是可变的,所以经常要在此来回切换。可能产生如下场景:
1) 要测试 findAll 方法,这个时候我们要去数据库里去先写个 count(*) 之后,才能写 assertEquals(result.size(),size) 。
2) 要测试 updateMember(), 要先去数据库里查一条数据,并记下之前的状态,然后才能写 upadate 的 TestCase 。
3) 要测试 insert ,要先去数据库要先看下某主键是否已经被使用。
4) 要测试 delete ,要先去数据库里面去查找是否某条数据存在。
如果说我们在跑某一个测试用例的时候,总是已经知道了数据表里面当前的数据状态,那么就省了这种频繁的来回切换。 DbUnit 就是为了这个而产生。
2. dbunit 基本原理
DbUnit is a JUnit extension (also usable with Ant) targeted at database-driven projects that, among other things, puts your database into a known state between test runs. This is an excellent way to avoid the myriad of problems that can occur when one test case corrupts the database and causes subsequent tests to fail or exacerbate the damage.(Dbunit 是 Junit 的一个扩张。用于基于数据的项目。在测试时,使你的数据库处于一种已知的状态。是一种避免因为某些测试用例失败导致相关的测试失败而产生的大量的问题。 )
Dbunit 基本原理就是在跑测试用例运行之前对数据表做用户定义的操作,清空不想要的数据,插入用户自定义的数据,使得该数据表处于用户知道的一种状态。而用户自定义的数据使用项目里的一个 xml 文件来表示。 Xml 文件里面的数据代表了当前的数据状态,用户可以通过 xml 文件了解当前数据表的状态。同时在测试用例跑完之后,用户可以定义使得数据表处于一种用户想要的状态。一个测试用例的运行过程如下:
图 1 :测试用例运行过程。
一般的一个测试用例结构如下:
package com.contentsearch.dao.hibernate.impl; import java.io.FileInputStream; import org.dbunit.DBTestCase; import org.dbunit.PropertiesBasedJdbcDatabaseTester; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.xml.FlatXmlDataSetBuilder; import org.dbunit.operation.DatabaseOperation; public class SampleDbunitTest extends DBTestCase { // 在构造函数里面设置数据库DB Connection的信息 public SampleDbunitTest() { System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "com.mysql.jdbc.Driver"); System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, "jdbc:mysql://localhost:3306/contentsearch?useUnicode=true&characterEncoding=UTF-8"); System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "root"); System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "123"); } @Override // 可选的覆盖,该方法代表运行该测试用力之前,要作的操作。默认就是CLEAN_INSERT protected DatabaseOperation getSetUpOperation() throws Exception { return DatabaseOperation.CLEAN_INSERT; } @Override // 可选的覆盖,该方法代表运行完该测试,要做的操作。默认就是NONE protected DatabaseOperation getTearDownOperation() throws Exception { return DatabaseOperation.NONE; } @Override // 这是必须实现的方法,返回的dataset代表数据表里面将要存放的数据。也就是当前该表的数据状态 protected IDataSet getDataSet() throws Exception { // TODO Auto-generated method stub return new FlatXmlDataSetBuilder().build(new FileInputStream( "sample.xml")); } public void testSample() { // 实现测试用例 } }
关于 getSetUpOperation 和 getTearDownOperation 的个解释:
DatabaseOperation.CLEAN_INSERT :先清空数据表里的数据,再插入 getDataSet 返回的数据到数据表中。是 getSetUpOperation 的默认状态 ( 这个操作会使得数据库始终处于只有几条固定数据的状态,也是我们最常用的一种方式 ) 。
DatabaseOperation.NONE: 不做任何事情。
还有 DatabaseOperation.UPDATE 、 DatabaseOperation.INSERT 、 DatabaseOperation.REFRESH 、 DatabaseOperation.DELETE 、 DatabaseOperation.DELETE_ALL 、 DatabaseOperation.TRUNCATE 、 CompositeOperation 、 TransactionOperation 和 IdentityInsertOperation 等几个状态,具体参考官网: http://dbunit.sourceforge.net/components.html#cleanInsert
3. 例子
假设有这样一张表:
CREATE TABLE `contentsearch`.`member` ( `id` bigint(20) unsigned NOT NULL auto_increment, `memberid` varchar(16) NOT NULL, `name` varchar(16) NOT NULL, `pass` varchar(16) NOT NULL, `email` varchar(20) NOT NULL, `gender` char(6) NOT NULL, `birthday` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=14 DEFAULT CHARSET=utf8
我们要对 IMemberDao 的实现类做如下三个方法的测试:
public List<Member> listAllMember(); //list all Members.
public Member findMemberById(long id); //find a Member by Id.
public boolean insertMember(Member member); //insert a member record.
假设我们要使数据表 member 处于如下状态:有三条用户自定义的记录,我们可以写一个 MemberSet.xml:
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<member id="1" memberid="ant" name="sky" pass="123"
email="[email protected]" gender="male" birthday="1985-06-21" />
<member id="2" memberid="ant" name="abing" pass="123"
email="[email protected]" gender="male" birthday="1985-06-21" />
<member id="3" memberid="ant" name="yblin" pass="123"
email="[email protected]" gender="male" birthday="1985-06-21" />
</dataset>
我们的测试用例可以这样写:
package com.contentsearch.dao.hibernate.impl; import java.io.FileInputStream; import java.util.List; import org.dbunit.DBTestCase; import org.dbunit.PropertiesBasedJdbcDatabaseTester; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.xml.FlatXmlDataSetBuilder; import org.dbunit.operation.DatabaseOperation; import com.contentsearch.dao.IMemberDao; import com.contentsearch.dao.pojo.Member; public class MemberHibernateDaoTest extends DBTestCase { IMemberDao memberDao = new MemberHibernateDao(); //设置数据库DB Connection的相关信息,因为dbunit测试用例需要对数据库操作。 public MemberHibernateDaoTest() { System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "com.mysql.jdbc.Driver"); System.setProperty( PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, "jdbc:mysql://localhost:3306/contentsearch?useUnicode=true&characterEncoding=UTF-8"); System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "root"); System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "123"); } @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(); } public void testInsert() { Member member = new Member(); member.setBirthday("1985-06-21"); member.setGender("男"); member.setEmail("[email protected]"); member.setMemberid("3333"); member.setName("sky"); member.setPass("1231333"); member.setId(4); memberDao.insertMember(member); Member member2 = memberDao.findMemberById(5); assertEquals(member.getName(), "sky"); } public void testListAllMember() { List<Member> members = memberDao.listAllMember(); assertEquals(members.size(), 3); } public void testListMemberById() { Member member2 = memberDao.findMemberById(1); assertEquals("sky",member2.getName()); } //设置 @Override protected IDataSet getDataSet() throws Exception { return new FlatXmlDataSetBuilder().build(new FileInputStream( "src/test/java/sqlmap/memberSet.xml")); } }
通过以上的测试用例,我们只需要关注工程里的 MemberSet.xml 文件就可以完全了解数据库当前的状态了。