注意DatabaseFixture的createProxy()方法,它將一個(gè)普通的DAO對(duì)象包裝為在事務(wù)范圍內(nèi)執(zhí)行的代理對(duì)象,即對(duì)于一個(gè)普通的DAO對(duì)象的方法調(diào)用前后,自動(dòng)地開啟事務(wù)并根據(jù)異常情況提交或回滾事務(wù)。
下面是UserDaoImpl的單元測(cè)試類:
public class UserDaoImplTest extends DatabaseFixture {
private UserDao userDao = new UserDaoImpl();
private UserDao proxy = (UserDao)createProxy(userDao);
@Test
public void testQueryUser() {
User user = newUser("test");
proxy.createUser(user);
User t = proxy.queryUser("test");
assertEquals(user.getEmail(), t.getEmail());
}
}
注意到UserDaoImplTest持有兩個(gè)UserDao引用,userDao是普通的UserDaoImpl對(duì)象,而proxy則是將userDao進(jìn)行了事務(wù)封裝的對(duì)象。
由于UserDaoImplTest從DatabaseFixture繼承,因此,@Before方法在每個(gè)@Test方法調(diào)用前自動(dòng)調(diào)用,這樣,每個(gè)@Test方法執(zhí)行前,數(shù)據(jù)庫(kù)都是一個(gè)經(jīng)過初始化的“干凈”的表。
對(duì)于普通的測(cè)試,如UserDao.queryUser()方法,直接調(diào)用proxy.queryUser()即可在事務(wù)內(nèi)執(zhí)行查詢,獲得返回結(jié)果。
對(duì)于異常測(cè)試,例如期待一個(gè)ResourceNotFoundException,不能直接調(diào)用proxy.queryUser()方法,否則,將得到一個(gè)UndeclaredThrowableException:
對(duì)DAO編寫單元測(cè)試 圖-3
這是因?yàn)橥ㄟ^反射調(diào)用拋出的異常被代理類包裝為UndeclaredThrowableException,因此,對(duì)于異常測(cè)試,只能使用原始的userDao對(duì)象配合TransactionCallback實(shí)現(xiàn):
@Test(expected=ResourceNotFoundException.class)
public void testQueryNonExistUser() throws Exception {
new TransactionCallback() {
protected Object doInTransaction() throws Exception {
userDao.queryUser("nonexist");
return null;
}
}.execute();
}
到此為止,對(duì)DAO組件的單元測(cè)試已經(jīng)實(shí)現(xiàn)完畢。下一步,我們需要使用HibernateTool自動(dòng)生成數(shù)據(jù)庫(kù)腳本,免去維護(hù)SQL語(yǔ)句的麻煩。相關(guān)的Ant腳本片段如下:
<target name="make-schema" depends="build" description="create schema">
<taskdef name="hibernatetool" classname="org.hibernate.tool.ant.HibernateToolTask">
<classpath refid="build-classpath"/>
</taskdef>
<taskdef name="annotationconfiguration" classname="org.hibernate.tool.ant.AnnotationConfigurationTask">
<classpath refid="build-classpath"/>
</taskdef>
<annotationconfiguration configurationfile="$/hibernate.cfg.xml"/>
<hibernatetool destdir="$">
<classpath refid="build-classpath"/>
<annotationconfiguration configurationfile="$/hibernate.cfg.xml"/>
<hbm2ddl
export="false"
drop="true"
create="true"
delimiter=";"
outputfilename="schema.sql"
destdir="$"
/>
</hibernatetool>
</target>
完整的Ant腳本以及Hibernate配置文件請(qǐng)參考項(xiàng)目工程源代碼。
利用HSQLDB,我們已經(jīng)成功地簡(jiǎn)化了對(duì)DAO組件進(jìn)行單元測(cè)試。我發(fā)現(xiàn)這種方式能夠找出許多常見的bug:
HQL語(yǔ)句的語(yǔ)法錯(cuò)誤,包括SQL關(guān)鍵字和實(shí)體類屬性的錯(cuò)誤拼寫,反復(fù)運(yùn)行單元測(cè)試可以不斷地修復(fù)許多這類錯(cuò)誤,而不需要等到通過Web頁(yè)面請(qǐng)求而調(diào)用DAO時(shí)才發(fā)現(xiàn)問題;
傳入了不一致或者順序錯(cuò)誤的HQL參數(shù)數(shù)組,導(dǎo)致Hibernate在運(yùn)行期報(bào)錯(cuò);
一些邏輯錯(cuò)誤,包括不允許的null屬性(常常由于忘記設(shè)置實(shí)體類的屬性),更新實(shí)體時(shí)引發(fā)的數(shù)據(jù)邏輯狀態(tài)不一致。
總之,單元測(cè)試需要根據(jù)被測(cè)試類的實(shí)際情況,編寫簡(jiǎn)單有效的測(cè)試用例。本文旨在給出一種編寫DAO組件單元測(cè)試的有效方法。