Hibernate單元測(cè)試的方法HSQLDB詳述
曾經(jīng)使用許多方法在數(shù)據(jù)庫(kù)和目標(biāo)代碼之間傳輸數(shù)據(jù)。從手動(dòng)編碼的SQL到JDO,然后再到EJB,我從未找到一種特別喜歡的方法。自從采用測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(TDD)作為指導(dǎo)原則以來(lái),這種不滿情緒變得更加強(qiáng)烈。
單元測(cè)試的障礙應(yīng)盡可能少。在關(guān)系數(shù)據(jù)庫(kù)中,障礙的范圍從外部依賴(數(shù)據(jù)庫(kù)在運(yùn)行嗎?)到保持關(guān)系模型和對(duì)象模型同步的速度。由于這些原因,保持?jǐn)?shù)據(jù)庫(kù)訪問(wèn)代碼與核心對(duì)象模型分離且無(wú)需涉及真實(shí)數(shù)據(jù)庫(kù)而進(jìn)行盡可能多的測(cè)試是很重要的。
通常這會(huì)導(dǎo)致我們進(jìn)入下面兩種模式之一。***種是具體化所有訪問(wèn)域?qū)ο蟮臄?shù)據(jù)以及數(shù)據(jù)與單獨(dú)類或接口之間的關(guān)系。這就是典型的能夠檢索、編輯、刪除和添加域?qū)嶓w的數(shù)據(jù)存儲(chǔ)對(duì)象。這在單元測(cè)試中是最容易模擬出來(lái)的,但趨向于把域模型對(duì)象作為不帶有任何關(guān)系行為的純數(shù)據(jù)對(duì)象。直接從父對(duì)象訪問(wèn)子記錄是最理想的,而不是將父對(duì)象處理為第三方類來(lái)決定子記錄。
其他方法已經(jīng)使訪問(wèn)接口的域?qū)ο筮M(jìn)入數(shù)據(jù)映射層(一種la Martin Fowler的數(shù)據(jù)映象模式)。這具有推動(dòng)域模型中的對(duì)象關(guān)系的優(yōu)點(diǎn),在域模型中,對(duì)象關(guān)系型接口只需表達(dá)一次即可。使用域模型的類不支持持久性機(jī)制,因?yàn)樗旧韮?nèi)在化到域模型中。這使代碼集中在設(shè)法解決的業(yè)務(wù)問(wèn)題,而很少關(guān)注對(duì)象關(guān)系型映射機(jī)制。
我的當(dāng)前項(xiàng)目涉及到處理大量的棒球統(tǒng)計(jì)數(shù)據(jù),并使用這些數(shù)據(jù)進(jìn)行模擬。因?yàn)閿?shù)據(jù)已經(jīng)在關(guān)系數(shù)據(jù)庫(kù)中,所以對(duì)于我來(lái)說(shuō),有機(jī)會(huì)開(kāi)發(fā)Hibernate對(duì)象關(guān)系型映射系統(tǒng)。我曾對(duì)Hibernate有很深刻的印象,但我遇到的一個(gè)問(wèn)題是,在使用Hibernate進(jìn)行單元測(cè)試的數(shù)據(jù)映射時(shí),設(shè)法插入一個(gè)間接層。該附加層非常脆弱,編寫(xiě)起來(lái)感到非常困難。實(shí)際部署版本簡(jiǎn)單地通過(guò)了特定于Hibernate的實(shí)現(xiàn)。更壞的情況是,模擬版本比真正的“產(chǎn)品級(jí)”版本更復(fù)雜,只因?yàn)槟M版本里沒(méi)有基本對(duì)象存儲(chǔ)器和帶有Hibernate的映射。
我也使用很多復(fù)雜的Hibernate查詢,想要對(duì)應(yīng)用程序的重要部分進(jìn)行單元測(cè)試。然而,對(duì)活動(dòng)的數(shù)據(jù)庫(kù)進(jìn)行測(cè)試不是好主意,因?yàn)檫@幾乎總是產(chǎn)生維護(hù)問(wèn)題。另外,由于測(cè)試***互相獨(dú)立,在測(cè)試上下文數(shù)據(jù)中使用相同的主鍵意味著必須在每次測(cè)試前創(chuàng)建代碼來(lái)清理數(shù)據(jù)庫(kù),當(dāng)涉及到大量關(guān)系時(shí)就成為一個(gè)實(shí)際問(wèn)題。
通過(guò)使用HSQLDB和Hibernate強(qiáng)大的模式生成工具,能夠?qū)?yīng)用程序映射層進(jìn)行單元測(cè)試,并在對(duì)象查詢中找到不計(jì)其數(shù)的bug,這在以前手工測(cè)試時(shí)是做不到的。利用下面的技術(shù)概述,可以在開(kāi)發(fā)過(guò)程中對(duì)整個(gè)應(yīng)用程序進(jìn)行測(cè)試,并且在測(cè)試有效區(qū)域內(nèi)沒(méi)有損害。
設(shè)置HSQLDB
以前使用HSQLDB 1.7.3.0 版。為了使用數(shù)據(jù)庫(kù)的內(nèi)存版本,需要激活org.hsqldb.JDBCDriver的靜態(tài)加載程序。當(dāng)獲得JDBC連接時(shí),就可以使用JDBC url例如jdbc:hspldb:mem:yourdb,這里’yourdb’就是想要使用的內(nèi)存數(shù)據(jù)庫(kù)的名稱。
因?yàn)槭褂肏ibernate (3.0 beta 4),所以我?guī)缀鯚o(wú)需接觸實(shí)際活動(dòng)的JDBC對(duì)象。相反,我可以讓Hibernate完成很多繁重的任務(wù),包括從Hibernate映射文件中自動(dòng)創(chuàng)建數(shù)據(jù)庫(kù)模式。因?yàn)镠ibernate創(chuàng)建自身專有的連接池,所以它會(huì)基于TestSchema類中的配置代碼自動(dòng)加載HSQLDB JDBC驅(qū)動(dòng)程序。下面就是該類的靜態(tài)的初始化程序。
public class TestSchema {static {HibernateUtil.setSessionFactory(config.buildSessionFactory()); |
Hibernate提供了許多不同的方式來(lái)配置該框架,包括程序方面的配置。上述代碼設(shè)置了連接池。注意,使用HSQLDB的內(nèi)存數(shù)據(jù)庫(kù)需要用戶名’sa’。還樣要確保指定一個(gè)空格作為口令。為了啟動(dòng)Hibernate的自動(dòng)模式生成功能,需設(shè)置hibernate.hbm2ddl.auto屬性為’creat-drop’。
實(shí)際測(cè)試 我的項(xiàng)目是處理將大量的棒球數(shù)據(jù),所以我添加了四個(gè)進(jìn)行映射的類(Player、PintchingStint、,BattingSint和FieldStint)。***創(chuàng)建Hibernate的會(huì)話工廠,并將其插入HibernateUtil類,該類只為Hibernate會(huì)話的整個(gè)應(yīng)用程序提供一個(gè)訪問(wèn)方法。HibernateUtil的代碼如下:
import org.hibernate.*; public class HibernateUtil {private static SessionFactory factory; public static synchronized Session getSession() {public static void setSessionFactory(SessionFactory factory) { |
因?yàn)樗写a(經(jīng)過(guò)單元測(cè)試的產(chǎn)品級(jí)代碼)都是從HibernateUtil獲取Hibernate會(huì)話,所以能在同一個(gè)位置對(duì)其進(jìn)行配置。為了對(duì)代碼的***位進(jìn)行單元測(cè)試而訪問(wèn)TestSchema類將會(huì)激活靜態(tài)初始化程序,該程序?qū)惭bHibernate并且將測(cè)試SessionFactory插入到HibernateUtil中。對(duì)于產(chǎn)品級(jí)代碼,可以使用標(biāo)準(zhǔn)hibernate.cfg.XML配置機(jī)制來(lái)初始化 SessionFactory。那么單元測(cè)試中的外部特征是什么?下面的測(cè)試代碼片段是用來(lái)檢查邏輯的,決定運(yùn)動(dòng)員在棒球聯(lián)盟比賽中是哪個(gè)位置的人選:
public void testGetEligiblePositions() throws Exception {FieldingStint stint1 = new FieldingStint("playerId", 2004, "SEA", Position.CATCHER);Set |
***次創(chuàng)建新Player實(shí)例并通過(guò)addPlayer()方法添加到TestSchema中。必須首先完成此步驟,因?yàn)镕idldStint類和Player類之間有外鍵關(guān)系。如果不首先添加該實(shí)例,在設(shè)法添加FieldingStint時(shí)將會(huì)出現(xiàn)外鍵約束違例。一旦測(cè)試上下文就位,就可以測(cè)試getEligiblePositions()方法來(lái)檢索校正數(shù)據(jù)。下面是在TsetSchema中addPlayer()方法的代碼。您將注意到使用Hibernate而不是bare-metal JDBC代碼:
public static void addPlayer(Player player) {Session session = HibernateUtil.getSession(); |
在單元測(cè)試中最重要的就是要保持測(cè)試實(shí)例是獨(dú)立的。因?yàn)樵摲椒ㄈ匀簧婕皵?shù)據(jù)庫(kù),所以需要一種方法在每個(gè)測(cè)試實(shí)例之前清理數(shù)據(jù)庫(kù)。在我的數(shù)據(jù)庫(kù)架構(gòu)中有四個(gè)表,所以我在TestSchemaz上編寫(xiě)了reset()方法,該方法從使用JDBC的表中刪除所有行。注意,因?yàn)镠SQLDB能識(shí)別外鍵,刪除表的順序是很重要的,下面是代碼:
public static void reset() throws SchemaException { |
當(dāng)確定在Hibernate 3.0中進(jìn)行大量刪除操作時(shí),應(yīng)該能從應(yīng)用程序中刪除直接JDBC的***一位。到此時(shí)為止,必須獲取數(shù)據(jù)庫(kù)連接并向數(shù)據(jù)庫(kù)直接提交SQL。 在確保沒(méi)有關(guān)閉連接的情況下,為了釋放資源,只關(guān)閉會(huì)話就足夠了。出于手工編寫(xiě)許多JCBC代碼來(lái)進(jìn)行開(kāi)發(fā)的習(xí)慣,***個(gè)版本關(guān)閉了JDBC連接。因?yàn)橥ㄟ^(guò)配置Hibernate創(chuàng)建的連接池只帶有一個(gè)鏈接,在***個(gè)之后就完全破壞了測(cè)試。一定要注意這種情況! 既然在測(cè)試類運(yùn)行時(shí)(設(shè)想運(yùn)行所有的測(cè)試實(shí)例)不能確定數(shù)據(jù)庫(kù)的狀態(tài),應(yīng)該在setUp()方法中包含數(shù)據(jù)庫(kù)清除,如下所示:
public void setUp() throws Exception { |
結(jié)束語(yǔ)
在使用像Hibernate這種復(fù)雜的O/R映射程序時(shí),必須能夠測(cè)試實(shí)際存在(real-live)的RDBMS,而不會(huì)發(fā)生任何針對(duì)已部署數(shù)據(jù)庫(kù)的爭(zhēng)論。雖然Hibernate有內(nèi)置模式生成工具,讓此類測(cè)試特別簡(jiǎn)單,但是在這里展示的例子并不排除Hibernate,并且可能與JDO或TopLink一起運(yùn)行。使用上面描述的設(shè)置,您不必離開(kāi)舒適的IDE環(huán)境,但仍然可以對(duì)代碼進(jìn)行大量測(cè)試。
【編輯推薦】
- Struts+Spring+Hibernate開(kāi)發(fā)入行真功夫
- 優(yōu)化Hibernate性能的幾點(diǎn)建議
- Hibernate 3新特性介紹及發(fā)展趨勢(shì)























