dbunit是一個基于junit擴展的數據庫測試框架。它提供了大量的類對與數據庫相關的操作進行了抽象和封裝,雖然在80%的情況,你只需使用它極少的api。它通過使用用戶自定義的數據集以及相關操作使數據庫處于一種可知的狀態,從而使得測試自動化、可重復和相對獨立。雖然不用dbunit也可以達到這種目的,但是我們必須為此付出代價(編寫大量代碼,測試及維護),既然有了這么優秀的開源框架,我們又何必再造輪子。

 

  • dbunit的原理

 

dbunit的與單元測試相關的兩個最重要的核心是org.dbunit.database.IDatabaseConnection org.dbunit.dataset.IDataSet ,前者是產品代碼使用的數據庫連接的一個簡單的封裝,后者是對單元測試人員自定義的數據集(通常以xml文件的形式存在,且xml文件的格式也有好幾種)的封裝。

 

還有一個很重要的咚咚就是org.dbunit.operation.DatabaseOperation該類是一個抽象類代表了對數據庫的操作,例如CUD以及其組合等, 它采用了退化的工廠模式,可直接通過它獲取其具體的子類(代表具體的某種操作)如下:

DatabaseOperation.UPDATE

DatabaseOperation.DELETE

DatabaseOperation.DELETE_ALL

DatabaseOperation.TRUNCATE

DatabaseOperation.REFRESH

DatabaseOperation.CLEAN_INSERT

DatabaseOperation.NONE

 

工作流程如下:

1)testcase.setup--->testcase.getConnection-->getDataSet----->operation.execute(
通常DatabaseOperation.CLEAN_INSERT)

2)testcase.testSomeMethod---->dao.someMethod

3)testcase.teardown---->operation.execute(
通常DatabaseOperation.DELETE_ALL或者DatabaseOperation.NONE)


 

  • 實戰

以一個真實的系統(share@mofile)為例,

  1. 建立一個測試數據庫
  2. 寫自定義的數據

<?xml version='1.0' encoding='UTF-8'?>

 

<dataset>

 

<FileType typeId='1' typeName='type1' />

 

<FileType typeId='2' typeName='type2' />

 

<FileType typeId='3' typeName='type3' />

 

<UserInSharedSystem userId='1' loginName='2' userNo='a' />

 

<DefaultFile fileId='1' fileName='a1' fileSize='10' fileStatus='1'

pickupCode='4414402888619758' releaseDate='2006-1-31 16:17:18'

storageFileHome='solar' storageFileId='1' userId='1' typeId='1'

description='ss' expiredDate='2006-1-31 16:17:18'

uploadDate='2006-1-31 16:17:18' extName='exe' totalPoints='0'

currentMonthPoints='0' lastMonthPoints='0' />

 

<DefaultFile fileId='2' fileName='a2' fileSize='20' fileStatus='1'

pickupCode='4414402888619759' releaseDate='2006-1-31 16:17:18'

storageFileHome='solar' storageFileId='2' userId='1' typeId='2'

description='ss' expiredDate='2006-1-31 16:17:18'

uploadDate='2006-1-31 16:17:18' extName='exe' totalPoints='0'

currentMonthPoints='0' lastMonthPoints='0' />

 

<DefaultFile fileId='3' fileName='a3' fileSize='30' fileStatus='1'

pickupCode='4414402888619768' releaseDate='2006-1-31 16:17:18'

storageFileHome='solar' storageFileId='3' userId='1' typeId='3'

description='ss' expiredDate='2006-1-31 16:17:18'

uploadDate='2006-1-31 16:17:18' extName='exe' totalPoints='0'

currentMonthPoints='0' lastMonthPoints='0' />

 

<FileTag tagId='1' tagName='t1' />

 

<FileTag tagId='2' tagName='t2' />

 

<FileTag tagId='3' tagName='t3' />

 

 

<FileTagRel fileId='1' tagId='1' />

 

<FileTagRel fileId='1' tagId='2' />

 

<FileTagRel fileId='2' tagId='1' />

 

<FileTagRel fileId='2' tagId='3' />

 

<FileTagRel fileId='3' tagId='2' />

 

</dataset>

注意數據的順序,否則會有約束違例,特別是外鍵約束

 

  1. 寫基本的測試類

package mofile.share.dao;

 

import javax.sql.DataSource;

 

 

 

import mofile.common.utils.SpringBeanProxy;

 

 

import org.dbunit.DatabaseTestCase;

import org.dbunit.database.DatabaseConnection;

import org.dbunit.database.IDatabaseConnection;

import org.dbunit.dataset.IDataSet;

import org.dbunit.operation.DatabaseOperation;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

 

/**

* 使用dbunit

*

* @author weip

* @time 2006-3-9 13:42:32

*

*/

public abstract class BaseDaoTest extends DatabaseTestCase {

 

protected final static ApplicationContext ctx;

 

protected IDatabaseConnection conn = null;

 

protected IDataSet dataSet = null;

 

static {

// String pkg = ClassUtils.classPackageAsResourcePath(Constants.class);

String[] paths = { "classpath:applicationContext-share-database.xml",

"classpath:applicationContext-share-hibernate.xml",

"applicationContext-share-property.xml"/*

* ,

* "classpath:applicationContext-central-database.xml"

*/};

ctx = new ClassPathXmlApplicationContext(paths);

SpringBeanProxy.setApplicationContext(ctx);

}

 

public BaseDaoTest(String methodName) {

super(methodName);

}

 

/**

*

* @author weip

* @time 2006-3-9 13:46:45 (non-Javadoc)

* @see org.dbunit.DatabaseTestCase#getConnection()

*

*/

 

protected IDatabaseConnection getConnection() throws Exception {

DataSource ds = (DataSource) ctx.getBean("shareDataSource");

 

conn = new DatabaseConnection(ds.getConnection());

 

return conn;

}

 

/**

*

* @author weip

* @time 2006-3-9 13:52:27 (non-Javadoc)

* @see org.dbunit.DatabaseTestCase#getSetUpOperation()

*

*/

 

protected DatabaseOperation getSetUpOperation() throws Exception {

return DatabaseOperation.CLEAN_INSERT;

}

 

/**

*

* @author weip

* @time 2006-3-9 13:52:31 (non-Javadoc)

* @see org.dbunit.DatabaseTestCase#getTearDownOperation()

*

*/

 

protected DatabaseOperation getTearDownOperation() throws Exception {

//this.closeConnection(conn);

//conn=null;

return DatabaseOperation.NONE;

}

 

 

}

 

  1. 寫具體的測試用例

package mofile.share.dao;

 

import java.io.FileInputStream;

import java.util.List;

 

import junit.framework.Test;

import junit.framework.TestSuite;

 

import mofile.share.domain.DefaultFile;

import mofile.share.domain.DefaultUser;

import mofile.share.domain.FileTag;

import mofile.share.domain.FileType;

import mofile.share.vo.FileForm;

 

import org.dbunit.dataset.IDataSet;

import org.dbunit.dataset.xml.FlatXmlDataSet;

import org.dbunit.operation.DatabaseOperation;

 

/**

*

* @author weip

* @time 2006-3-9 11:13:34

*

*/

public class FileDaoTest extends BaseDaoTest {

private FileDao fileDao;

 

protected void setUp() throws Exception {

super.setUp();

 

/*try {

DatabaseOperation.CLEAN_INSERT.execute(conn, dataSet);

} finally {

conn.close();

}*/

fileDao = (FileDao) ctx.getBean("fileDao");

}

 

protected void tearDown() throws Exception {

super.tearDown();

}

 

public FileDaoTest(String methodName) {

super(methodName);

}

 

public static Test suite() {

TestSuite suite = new TestSuite();

 

suite.addTest(new FileDaoTest("testQueryAllFiles"));

suite.addTest(new FileDaoTest("testQueryFilesByFileType"));

 

suite.addTest(new FileDaoTest("testAddFiles"));

 

suite.addTest(new FileDaoTest("testRemoveObject"));

 

return suite;

}

 

/**

*

*  @author weip

*  @time 22:53:34 2006-3-16

*  @see org.dbunit.DatabaseTestCase#getDataSet()

*/

protected IDataSet getDataSet() throws Exception {

dataSet = new FlatXmlDataSet(new FileInputStream("d:/Projects/Share/Mofile_share/htdocs/WEB-INF/classes/mofile/share/dao/testFileDB.xml"));

return dataSet;

 

}

 

/**

*

* @author weip

* @time 22:53:17 2006-3-16

* @throws Exception

*/

public void testAddFiles() throws Exception {

FileType fileType = new FileType();

fileType.setTypeId(new Integer(1));

 

FileTag fileTag = new FileTag();

fileTag.setTagName("dev3");

 

DefaultUser user = new DefaultUser();

// user.setLoginName("");

// user.setUserNo(new Long(1));

user.setUserId(new Long(1));

 

DefaultFile file = new DefaultFile();

 

file.setFileName("file001");

file.setFileSize(20);

file.setFileStatus(1);

file.setPickupCode("0001");

file.setStorageFileHome("xxx");

file.setStorageFileId(new Long(1));

file.setUser(user);

file.setFileType(fileType);

file.addTag(fileTag);

 

fileDao.saveObject(file);

 

DefaultFile fileafter=fileDao.getFileByStorageFileId("xxx",new Long(1));

 

assertNotNull(fileafter);

 

assertEquals("file001",fileafter.getFileName());

 

 

}

 

/**

*

* @author weip

* @time 2006-3-9 14:01:05

* @throws Exception

*/

public void testQueryAllFiles() throws Exception {

FileForm frm = new FileForm();

frm.setFileStatus(-1);

frm.setFileType(-1);

 

List list = fileDao.queryFile(frm);

 

assertEquals(3, list.size());

 

}

 

/**

*

* @author weip

* @time 22:47:01 2006-3-16

* @throws Exception

*/

public void testQueryFilesByFileType() throws Exception {

FileForm frm = new FileForm();

frm.setFileStatus(-1);

frm.setFileType(1);

 

List list = fileDao.queryFile(frm);

 

assertEquals(1, list.size());

 

}

 

/**

*

* @author weip

* @throws Exception

* @time 23:10:33 2006-3-16

*/

public void testRemoveObject() throws Exception {

 

fileDao.removeObject(DefaultFile.class, new Long(2));

 

FileForm frm = new FileForm();

frm.setFileStatus(-1);

frm.setFileType(-1);

 

List list = fileDao.queryFile(frm);

 

assertEquals(2, list.size());

 

}

 

 

}

 

對比以下以前寫的測試用例

public class FileDaoImplTestCase extends BaseDaoTestCase {

private FileDao fileDao;

 

protected void setUp() throws Exception {

super.setUp();

fileDao = (FileDao) ctx.getBean("fileDao");

}

 

protected void tearDown() throws Exception {

super.tearDown();

}

 

public FileDaoImplTestCase(String methodName) {

super(methodName);

}

 

public static Test suite() {

TestSuite suite = new TestSuite();

// suite.addTest(new FileDaoImplTest("testRemoveObject"));

 

// suite.addTest(new UserDaoImplTest("testSaveObject"));

// suite.addTest(new FileDaoImplTestCase("testSaveObject"));

// suite.addTest(new FileDaoImplTestCase("testQueryFileByTag"));

//suite.addTest(new FileDaoImplTestCase("testQueryFile4Pagenation"));

suite.addTest(new FileDaoImplTestCase("testUpdateLastMonthPoints"));

 

return suite;

}

 

/*

* Test method for

* 'mofile.share.dao.impl.BaseHibernateDaoImpl.getObject(Class,

* Serializable)'

*/

public void testGetObject() {

 

}

 

/**

* 插入一條文件記錄 ,注意插入記錄需檢查是否存在重復的filetag

*

* @author weip

* @time 16:42:20 2006-1-15

* @throws Exception

*

* Hibernate: insert into DefaultFile (fileName, fileSize, fileStatus,

* pickupCode, releaseDate, storageFileHome, storageFileId, userId, typeId)

* values (?, ?, ?, ?, ?, ?, ?, ?, ?) Hibernate: insert into FileTag

* (tagName) values (?) Hibernate: insert into FileTagRel (fileId, tagId)

* values (?, ?)

*

*/

public void testSaveObject() throws Exception {

FileType fileType = new FileType();

fileType.setTypeId(new Integer(1));

 

FileTag fileTag = new FileTag();

fileTag.setTagName("dev3");

 

DefaultUser user = new DefaultUser();

// user.setLoginName("");

// user.setUserNo(new Long(1));

user.setUserId(new Long(1));

 

DefaultFile file = new DefaultFile();

 

file.setFileName("file001");

file.setFileSize(20);

file.setFileStatus(1);

file.setPickupCode("0001");

file.setStorageFileHome("xxx");

file.setStorageFileId(new Long(1));

file.setUser(user);

file.setFileType(fileType);

file.addTag(fileTag);

 

fileDao.saveObject(file);

 

}

 

/**

*

* 刪除一條文件記錄

*

* @author weip

* @time 16:47:29 2006-1-15

*

* Hibernate: select defaultfil0_.fileId as fileId2_, defaultfil0_.fileName

* as fileName2_, defaultfil0_.fileSize as fileSize2_,

* defaultfil0_.fileStatus as fileStatus2_, defaultfil0_.pickupCode as

* pickupCode2_, defaultfil0_.releaseDate as releaseD6_2_,

* defaultfil0_.storageFileHome as storageF7_2_, defaultfil0_.storageFileId

* as storageF8_2_, defaultfil0_.userId as userId2_, defaultfil0_.typeId as

* typeId2_, defaultuse1_.userId as userId0_, defaultuse1_.loginName as

* loginName0_, defaultuse1_.userNo as userNo0_, filetype2_.typeId as

* typeId1_, filetype2_.typeName as typeName1_ from DefaultFile defaultfil0_

* left outer join UseInSharedSystem defaultuse1_ on

* defaultfil0_.userId=defaultuse1_.userId left outer join FileType

* filetype2_ on defaultfil0_.typeId=filetype2_.typeId where

* defaultfil0_.fileId=? Hibernate: select tagset0_.fileId as fileId__,

* tagset0_.tagId as tagId__, filetag1_.tagId as tagId0_, filetag1_.tagName

* as tagName0_ from FileTagRel tagset0_ inner join FileTag filetag1_ on

* tagset0_.tagId=filetag1_.tagId where tagset0_.fileId=? Hibernate: delete

* from FileTagRel where fileId=? Hibernate: delete from DefaultFile where

* fileId=?

*/

public void testRemoveObject() {

 

fileDao.removeObject(DefaultFile.class, new Long(2));

 

}

 

/**

* 測試按標簽獲取文件

*

* @author weip

* @throws Exception

* @time 2006-1-24 20:12:07

*/

public void testQueryFileByTag() throws Exception {

 

List list = fileDao.queryFileByTag(54, 0, 10);

 

assertNotNull(list);

 

DefaultFile file = null;

if (list.size() > 0)

file = (DefaultFile) list.get(0);

}

 

public void testQueryFile4Pagenation() throws Exception {

FileForm frm = new FileForm();

frm.setFileStatus(-1);

frm.setFileType(-1);

fileDao.queryFileSum(frm);

}

 

/**

* 測試 批量更新上月得分總數

*

* @author weip

* @throws Exception

* @time 2006-2-21 16:07:32

*/

public void testUpdateLastMonthPoints() throws Exception {

fileDao.updateLastMonthPoints();

assertTrue(true);

 

}

 

}

 

 

  • 總結

 

通過與以前的例子比較發現:使用dbunittestcase更自動化和可重復,以前寫的testcase與數據庫中的數據嚴重耦合,所以一般都不敢寫斷言,寫了之后怕數據又發生變化,所以測試也是不可重復,并且也不是自動化,因為沒有斷言,你不得不測試完之后還得檢查數據庫。

 

當然dbunit也許并不是銀彈,它在并發測試的時候得表現我沒有實踐過,也不敢妄下斷言,而且是不是應該另外再建一個同樣的數據專門測試dao還值得思考

我們在項目中為每個開發人員自建一個數據庫解決并發問題,也許這個方案并非最佳,但實用

dbunit是一個基于junit擴展的數據庫測試框架。它提供了大量的類對與數據庫相關的操作進行了抽象和封裝,雖然在80%的情況,你只需使用它極少的api。它通過使用用戶自定義的數據集以及相關操作使數據庫處于一種可知的狀態,從而使得測試自動化、可重復和相對獨立。雖然不用dbunit也可以達到這種目的,但是我們必須為此付出代價(編寫大量代碼,測試及維護),既然有了這么優秀的開源框架,我們又何必再造輪子。