|
|
從概念上說,持久層可以分為3層,而iBATIS使得對(duì)這些不同的層進(jìn)行單元測試都變得非常簡單,如圖13-1所示。
|
l 測試映射層(mapping layer)本身,包括各個(gè)映射、所有的SQL語句,以及這些SQL語句被映射到的那些領(lǐng)域?qū)ο蟆?/p>
l 測試DAO層,這使你可以對(duì)DAO層中的任何特定于持久化的邏輯進(jìn)行測試。
l 在DAO的消費(fèi)層中進(jìn)行測試。
13.1.1 對(duì)映射層進(jìn)行單元測試
對(duì)映射層所進(jìn)行的單元測試,可能是在大部分應(yīng)用程序中所發(fā)生的最低層次的單元測試了。此過程包括對(duì)SQL語句以及這些語句所映射到的領(lǐng)域?qū)ο筮M(jìn)行測試。這意味著我們將需要一個(gè)用于進(jìn)行測試的數(shù)據(jù)庫實(shí)例。
2 1. 測試用數(shù)據(jù)庫實(shí)例
測試用數(shù)據(jù)庫實(shí)例可能是創(chuàng)建于你實(shí)際使用的數(shù)據(jù)庫管理系統(tǒng)(例如,Oracle或者微軟的SQL Server)中的一個(gè)真實(shí)實(shí)例。如果你的環(huán)境對(duì)于單元測試是友好的,那么只需要簡單地更改一下配置就可以運(yùn)行單元測試了。如果打算使用非標(biāo)準(zhǔn)數(shù)據(jù)庫特征(例如,存儲(chǔ)過程),那么就可能有必要使用真實(shí)的數(shù)據(jù)庫實(shí)例。存儲(chǔ)過程和其他一些非可移植的數(shù)據(jù)庫設(shè)計(jì)時(shí)選擇,會(huì)使得對(duì)數(shù)據(jù)庫進(jìn)行單元測試變得很難,除非使用真實(shí)的數(shù)據(jù)庫實(shí)例。
使用真實(shí)的數(shù)據(jù)庫實(shí)例的缺點(diǎn)是,只有連接到網(wǎng)絡(luò)才能進(jìn)行單元測試。或者也可以使用某個(gè)真實(shí)數(shù)據(jù)庫的一個(gè)本地實(shí)例,但這意味著單元測試在運(yùn)行之前將需要額外的本地環(huán)境設(shè)置。無論使用這兩種方法中的哪一種,你都將面對(duì)同一個(gè)問題,即每次測試都必須重建測試數(shù)據(jù),甚至可能需要重建測試套件(test suite)之間的模式,或者是每個(gè)單元測試之間的模式。即使是在大型的企業(yè)級(jí)的數(shù)據(jù)庫服務(wù)器上,要完成以上任務(wù)也需要花費(fèi)很長時(shí)間。另一個(gè)問題是,由于使用的數(shù)據(jù)庫是集中式的,多個(gè)開發(fā)人員同時(shí)進(jìn)行單元測試時(shí)就可能會(huì)導(dǎo)致沖突。所以,必須使用不同的數(shù)據(jù)庫模式來隔離每一個(gè)開發(fā)人員。正如你所見,這種方法的普遍問題就是,單元測試取決于相當(dāng)多的基礎(chǔ)設(shè)施,而這對(duì)于大多數(shù)經(jīng)驗(yàn)豐富的測試驅(qū)動(dòng)程序的開發(fā)人員來說是不夠完美的。
Java開發(fā)人員是非常幸運(yùn)的,因?yàn)樗麄冎辽儆幸环N非常棒的內(nèi)存(in-memory)數(shù)據(jù)庫可以使用,這種數(shù)據(jù)庫可以使得對(duì)相對(duì)標(biāo)準(zhǔn)的數(shù)據(jù)庫設(shè)計(jì)進(jìn)行單元測試變得非常簡單。HSQLDB是一個(gè)完全用Java寫成的內(nèi)存數(shù)據(jù)庫。它既不需要磁盤上的任何文件也不需要連接網(wǎng)絡(luò)就能夠正常工作。此外,它還能夠重新生成來自典型數(shù)據(jù)庫(例如Oracle和微軟的SQL Server)的大部分?jǐn)?shù)據(jù)庫設(shè)計(jì)。即使由于設(shè)計(jì)過于復(fù)雜(例如使用了存儲(chǔ)過程)而導(dǎo)致HSQLDB不能重建整個(gè)數(shù)據(jù)庫,它也仍然能夠重新生成該數(shù)據(jù)庫的絕大部分。HSQLDB允許快速重建數(shù)據(jù)庫,包括數(shù)據(jù)庫模式和測試數(shù)據(jù)。iBATIS自己的單元測試套件就是使用HSQLDB在每個(gè)單獨(dú)的測試之間重建數(shù)據(jù)庫模式和測試數(shù)據(jù)。我們親自使用HSQLDB測試了由將近1 000個(gè)數(shù)據(jù)庫相關(guān)的測試構(gòu)成的測試套件,運(yùn)行時(shí)間不到30秒。
有關(guān)HSQLDB的更多信息,請(qǐng)?jiān)L問網(wǎng)頁http://hsqldb.sourceforge.net/。另外,可能會(huì)讓微軟的.NET高興的一個(gè)消息是,已經(jīng)有人發(fā)起對(duì)HSQLDB的移植了,同時(shí)也有人開始創(chuàng)建其他的內(nèi)存數(shù)據(jù)庫了。
3 2. 數(shù)據(jù)庫腳本
現(xiàn)在已經(jīng)有了數(shù)據(jù)庫實(shí)例,那么數(shù)據(jù)庫模式和測試數(shù)據(jù)應(yīng)該如何構(gòu)造和創(chuàng)建呢?你可能已經(jīng)有了可以用來創(chuàng)建數(shù)據(jù)庫模式以及測試數(shù)據(jù)的數(shù)據(jù)庫腳本了。理想情況下,你應(yīng)該將這些腳本納入到版本控制系統(tǒng)(例如CVS或者Subversion)中。這些腳本應(yīng)該和應(yīng)用程序中的其他代碼一樣被同等地對(duì)待。即使你對(duì)自己使用的數(shù)據(jù)庫沒有控制權(quán),你也應(yīng)該定期從擁有控制權(quán)的人那里獲得相應(yīng)的更新。應(yīng)用程序的源代碼與數(shù)據(jù)庫腳本應(yīng)該始終保持同步,并且單元測試本來就是確保它們同步的。每當(dāng)運(yùn)行單元測試套件時(shí),你還應(yīng)該運(yùn)行這些腳本來重新創(chuàng)建數(shù)據(jù)庫模式。使用這種方法,可以很容易地將數(shù)據(jù)庫創(chuàng)建腳本需要的新集提交給版本控制系統(tǒng),然后運(yùn)行單元測試以便確定腳本更新是否給應(yīng)用程序帶來了問題。這是最理想的情況。如果使用內(nèi)存關(guān)系數(shù)據(jù)庫(例如HSQLDB)來運(yùn)行測試,則可能需要另外一個(gè)步驟來轉(zhuǎn)換數(shù)據(jù)庫模式??梢钥紤]使這個(gè)轉(zhuǎn)換過程自動(dòng)化,以便避免手工編程可能出現(xiàn)的錯(cuò)誤,加快集成的速度。
4 3. iBATIS配置文件(例如SqlMapConfig.xml)
為了進(jìn)行單元測試,你可能想要使用一個(gè)獨(dú)立的iBATIS配置文件。配置文件用于控制數(shù)據(jù)源和事務(wù)管理器的配置,它在測試環(huán)境和產(chǎn)品環(huán)境中可能會(huì)完全不同。例如,產(chǎn)品環(huán)境可能會(huì)是一個(gè)像J2EE應(yīng)用程序服務(wù)器這樣的受管理環(huán)境。在這樣的環(huán)境下,一個(gè)受管理的DataSource實(shí)例可能是從JNDI中檢索得到的。你還可能會(huì)在產(chǎn)品環(huán)境中利用全局事務(wù)。然而,在測試環(huán)境中,你的應(yīng)用程序可能不會(huì)運(yùn)行在服務(wù)器中;而只是配置了一個(gè)簡單DataSource,使用的也是局部事務(wù)。分別進(jìn)行測試環(huán)境配置和產(chǎn)品環(huán)境配置的最簡單的方式就是,使用不同的iBATIS配置文件,這兩份配置文件引用相同的一組SQL映射文件。
5 4. iBATIS SqlMapClient單元測試
現(xiàn)在所有的先決條件都已經(jīng)準(zhǔn)備好了,這些先決條件包括數(shù)據(jù)庫實(shí)例、自動(dòng)構(gòu)建數(shù)據(jù)庫的腳本,以及用于測試的配置文件,接下來可以開始創(chuàng)建單元測試了。代碼清單13-1即是一個(gè)使用JUnit來創(chuàng)建簡單單元測試的例子。
代碼清單13-1 SqlMapClient單元測試示例
|
|
代碼清單13-1中的示例使用了針對(duì)Java的JUnit單元測試框架。(可以在www.junit.org上找到更多有關(guān)JUnit的信息。對(duì)于.NET Framework,也有相似的工具,包括NUnit,可以從網(wǎng)頁www.nunit.org上下載得到。)在我們的設(shè)置方法中
以上就是測試映射層所需做的全部工作了。接下來要測試的層是DAO層,假定你的應(yīng)用程序有DAO層。
13.1.2 對(duì)DAO進(jìn)行單元測試
DAO層是一個(gè)抽象層,因此根據(jù)其本質(zhì),DAO應(yīng)該非常容易測試。DAO也使得對(duì)DAO層用戶的測試變得更加簡單。本節(jié),將討論測試DAO本身。DAO通常被分離為一個(gè)接口和一個(gè)實(shí)現(xiàn)。由于我們將直接測試DAO,因此接口將不起作用。我們將直接對(duì)DAO實(shí)現(xiàn)進(jìn)行測試。這可能同DAO模式的工作方式是相反的,但是這恰好體現(xiàn)了單元測試的好處——它把那些壞習(xí)慣清除出我們的系統(tǒng)!
如果可能的話,對(duì)DAO層的測試應(yīng)該不涉及數(shù)據(jù)庫以及底層的基礎(chǔ)設(shè)施。DAO層是持久化實(shí)現(xiàn)的一個(gè)接口,但是在測試DAO層時(shí),我們更感興趣的是測試DAO層內(nèi)部的東西,而不是測試DAO層之外的東西。
測試DAO的復(fù)雜度僅僅取決于DAO實(shí)現(xiàn)。例如,測試一個(gè)JDBC DAO可能非常困難。你需要一個(gè)很好的模擬框架來代替所有典型的JDBC組件,如Connection、ResultSet和Prepared- Statement等。即使是這樣,要利用模擬對(duì)象來管理這樣復(fù)雜的API也非常麻煩。而模擬iBATIS SqlMapClient接口則簡單得多。下面就來試一試。
6 1. 利用模擬對(duì)象來對(duì)DAO進(jìn)行單元測試
模擬對(duì)象是指為了進(jìn)行單元測試而用來替換實(shí)際實(shí)現(xiàn)的對(duì)象。模擬對(duì)象通常沒有很強(qiáng)的功能性;它們只用于滿足某個(gè)單一的情況,以使得單元測試僅僅關(guān)注其應(yīng)該關(guān)注的部分,而不需要擔(dān)心復(fù)雜度的增加。我們將在下面的例子中使用這些模擬對(duì)象來示范一種測試DAO層的方法。
在我們的示例中,將使用一個(gè)簡單的DAO。我們將不考慮iBATIS DAO框架,因此就不需要擔(dān)心事務(wù)以及諸如此類的東西了。這個(gè)示例的目的就是,示范如何測試DAO層,無論你使用的是哪一種DAO框架(只要確實(shí)使用了它)。
首先,來考察一下要進(jìn)行測試的DAO。代碼清單13-2給出了一個(gè)SqlMapPersonDao實(shí)現(xiàn),它調(diào)用了一個(gè)和13.1.1節(jié)中給出的示例相似的SQL映射文件。
代碼清單13-2 測試一個(gè)簡單的DAO
請(qǐng)注意在代碼清單13-2中我們是如何把SqlMapClient注入到DAO的構(gòu)造函數(shù)中的。這為對(duì)DAO進(jìn)行單元測試提供了一種簡易的方式,因?yàn)槲覀冎恍枰MSqlMapClient接口就可以了。顯然,這是一個(gè)非常簡單的示例,沒有對(duì)其進(jìn)行太多的測試,但是每一個(gè)測試都非常重要。代碼清單13-3顯示了用來模擬SqlMapClient并且測試getPerson()方法的單元測試。
代碼清單13-3 含有模擬SqlMapClient的PersonDao單元測試
代碼清單13-3中給出的示例使用了JUnit以及JMock這個(gè)Java對(duì)象模擬框架。正如代碼清單13-3中加粗的部分所顯示的那樣,利用JMock來模擬SqlMapClient接口實(shí)現(xiàn),使我們能夠單獨(dú)測試DAO的行為,而無需顧慮實(shí)際的SqlMapClient實(shí)現(xiàn),也就不需要考慮與其相關(guān)的SQL語句、XML文件,還有數(shù)據(jù)庫了。JMock是一個(gè)非常好用的工具,可以在www.jmock.org上找到更多有關(guān)它的信息。你可能已經(jīng)猜到了,還有一個(gè)針對(duì).NET的模擬框架,稱為NMock,有關(guān)它的更多信息請(qǐng)參考http://nmock.org。
13.1.3 對(duì)DAO的消費(fèi)層進(jìn)行單元測試
應(yīng)用程序中那些使用DAO層的其他層稱為DAO層的消費(fèi)者(consumer)。DAO模式使得你可以在不依賴于持久層的任何功能的情況下測試這些消費(fèi)者的功能。一個(gè)好的DAO實(shí)現(xiàn)應(yīng)該有一個(gè)能夠很好地描述其可用功能的接口。測試消費(fèi)層的關(guān)鍵在于獲得此接口??疾齑a清單13-4中的接口,你就會(huì)發(fā)現(xiàn)上一節(jié)中所描述的getPerson()方法。
代碼清單13-4 簡單的DAO接口
要開始測試DAO層的消費(fèi)者,所需要的就只是代碼清單13-4中所給出的接口。我們甚至根本不需要一個(gè)完整的實(shí)現(xiàn)。利用JMock,我們能夠很輕松地模擬getPerson()方法所預(yù)期的行為??紤]如下這個(gè)使用了PersonDao接口的服務(wù)(見代碼清單13-5)。
代碼清單13-5 使用PersonDao接口的服務(wù)
我們的單元測試的目標(biāo)并不是DAO——而是getValidatedPerson()方法中的業(yè)務(wù)邏輯,例如它所執(zhí)行的各種驗(yàn)證。方法中的每一個(gè)驗(yàn)證可能都是一個(gè)私有方法,為了便于討論,假設(shè)此處我們只測試私有接口。
多虧了之前的PersonDao接口,在沒有數(shù)據(jù)庫的情況下測試getValidatedPerson()方法也很容易。所需要做的只是模擬PersonDao接口實(shí)現(xiàn),然后將該模擬實(shí)現(xiàn)傳遞給服務(wù)的構(gòu)造函數(shù),最后調(diào)用getValidatedPerson()方法即可。代碼清單13-6給出了完成上述工作的單元測試。
代碼清單13-6 使用模擬而不是真實(shí)的DAO,以避免訪問數(shù)據(jù)庫
我們?cè)僖淮瓮瑫r(shí)使用了JUnit和JMock。正如你在代碼清單13-6中所見到的那樣,這種測試方式在應(yīng)用程序的各個(gè)層都是一致的。這是很有好處的,因?yàn)樗梢詭硪子诰S護(hù)的凝練的單元測試。
有關(guān)iBATIS中的單元測試,我們就介紹到這。實(shí)際上網(wǎng)上有很多很好的關(guān)于單元測試資源。使用Google搜索一下“unit test(單元測試)”,就可以找到很多相關(guān)的資源,它們可以幫助你迅速提高單元測試的能力,甚至你還可能發(fā)現(xiàn)比本書使用的方法更好的單元測試方法。
聯(lián)系客服