??? ??? 在做 Java 企業(yè)程序的時(shí)候,不可避免地要和外部資源打交道,比如數(shù)據(jù)庫(kù), Http 請(qǐng)求等。對(duì)于這些外部資源的處理,我們可采取的操作或者是直接處理或者是模擬處理。當(dāng)我們使用 Webwork , Spring , Hibernate 等框架時(shí),我們要測(cè)試的并不僅僅是 Java 代碼,我們還要測(cè)試依賴于這些框架的配置文件等等。因此,對(duì)于數(shù)據(jù)持久化的測(cè)試, Mock 方法是行不通的,我們需要真實(shí)地測(cè)試數(shù)據(jù)庫(kù)操作。對(duì)于持久化測(cè)試來說,重要的是創(chuàng)造出已知的“干凈的”的準(zhǔn)備數(shù)據(jù)。如果我們?cè)跍y(cè)試一個(gè)持久化方法前不能確定數(shù)據(jù)庫(kù)到底存著什么數(shù)據(jù),我們只能通過反復(fù)地查看數(shù)據(jù)庫(kù)數(shù)據(jù)來驗(yàn)證測(cè)試方法的正確性了(這就是我和大多數(shù)人以前使用的最“直接”的方法)。現(xiàn)在就讓我們使用 DbUnit ,來更好的更自動(dòng)化的測(cè)試持久化操作吧!
??? ??? 先介紹一下 DbUnit 。 DbUnit 是一個(gè) JUnit 擴(kuò)展,適用于數(shù)據(jù)驅(qū)動(dòng)的程序。使用 DbUnit ,可以在測(cè)試運(yùn)行期間將數(shù)據(jù)庫(kù)的數(shù)據(jù)處于已知狀態(tài),這樣在測(cè)試時(shí)可以方便地寫出測(cè)試斷言,也能自動(dòng)地完成對(duì)數(shù)據(jù)持久化方法的測(cè)試。在使用上, DbUnit 也很簡(jiǎn)單, 它提供了大量的類對(duì)與數(shù)據(jù)庫(kù)相關(guān)的操作進(jìn)行了抽象和封裝,大多數(shù)情況下你只需要使用少量簡(jiǎn)單的 API 。
??? ??? 下面我通過一個(gè)實(shí)際的小例子,介紹一下如何使用 DbUnit 。我也是剛剛使用上了 DbUnit ,經(jīng)驗(yàn)上不是很豐富,如果文中有不對(duì)的地方,也歡迎指正。這個(gè)例子很簡(jiǎn)單,我將較為詳細(xì)地說明如何使用 Hibernate 和 DbUnit 進(jìn)行測(cè)試。
??? ??? 測(cè)試第一步,準(zhǔn)備數(shù)據(jù)集。操作的數(shù)據(jù)表就是如下的 Account 表(使用的數(shù)據(jù)庫(kù)為Mysql):
????id?
bigint
?
not
?
null
?auto_increment,
????name?
varchar
(
50
)?
not
?
null
,
????
primary
?
key
(id)
)
character
?
set
?gbk;
??? ??? 至于 Account 類,映射文件等這里就不給出了。AccountDAO 接口很簡(jiǎn)單,只有兩個(gè)方法:
????
void
?insert(Account?a);
????List
<
Account
>
?findAll();
}
???
??? ??? 實(shí)現(xiàn)類如下:
????
???? 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;
????}
}
??? ??? 在測(cè)試前,要準(zhǔn)備出數(shù)據(jù)表中要裝入的數(shù)據(jù)(也就是數(shù)據(jù)集),這里給出與Account表對(duì)應(yīng)的數(shù)據(jù)集文件 Accout.xml 內(nèi)容如下:
<
dataset
>
????
<
Account?
id
="1"
?name
="kafka"
/>
????
<
Account?
id
="2"
?name
="0102"
/>
</
dataset
>
? ??
??? ??? 數(shù)據(jù)集就是一個(gè)
xml
文件,
<dataset>
中的每個(gè)節(jié)點(diǎn)對(duì)應(yīng)的就是一條表數(shù)據(jù)記錄(一個(gè)
dataset文件可以對(duì)應(yīng)多個(gè)數(shù)據(jù)表記錄
)。這里的
<Account>
節(jié)點(diǎn)對(duì)應(yīng)的就是
Account
表,屬性就是表中的字段,屬性值就是字段值了。在做測(cè)試時(shí),數(shù)據(jù)集中的內(nèi)容可以手動(dòng)敲進(jìn)去,也可以通過工具將數(shù)據(jù)庫(kù)中的數(shù)據(jù)導(dǎo)出來。
對(duì)于數(shù)據(jù)集的詳細(xì)信息,可參考
http://dbunit.sourceforge.net/components.html#FlatXmlDataSet
。
? ??
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 就是其使用的默認(rèn)實(shí)現(xiàn), AccountHibernateDAOTest 構(gòu)造函數(shù)的作用是完成數(shù)據(jù)庫(kù)連接參數(shù)的設(shè)置。 protected IDataSet getDataSet() 實(shí)現(xiàn)了裝載數(shù)據(jù)集到 IDataSet 中。 getSetUpOperation 和 getTearDownOperation 是可選的方法,返回的 DatabaseOperation 為 DBTestCase 在 SetUp 和 TearDown 中將執(zhí)行的操作, getSetUpOperation 默認(rèn)的操作為 DatabaseOperation.CLEAN_INSERT ,也就是先清空數(shù)據(jù)表中的數(shù)據(jù)再插入數(shù)據(jù)集中的數(shù)據(jù)到數(shù)據(jù)表中。getTearDownOperation 默認(rèn)的操作為 DatabaseOperation.NONE ,就是什么也不處理。可選的操作還有幾個(gè),可參考文檔進(jìn)行設(shè)置,但默認(rèn)的設(shè)置是最通用的了。 testDataset ()只是測(cè)試數(shù)據(jù)集中的數(shù)據(jù)和裝載到數(shù)據(jù)庫(kù)中數(shù)據(jù)是否一致。