沒有顯示 getJDBCConnection() 方法,因?yàn)樗膶?shí)現(xiàn)取決于希望如何獲得 JDBC 連接:當(dāng) DataSource 為 Serializable 時(shí)通過應(yīng)用服務(wù)器的 JNDI 樹,或者直接使用 JDBC。
getDbUnitConnection() 方法返回 DbUnit 的一個(gè)到數(shù)據(jù)庫的連接。DbUnit 的 DatabaseConnection 構(gòu)造函數(shù)可以帶一個(gè) schema 名作為參數(shù)。這樣,不必在所有表名前面加上 schema 名的前綴了。
getFlatXmlDataSet() 方法用位于類路徑上的一個(gè) XML 文件的內(nèi)容創(chuàng)建 DbUnit 數(shù)據(jù)集。
后,該實(shí)際將數(shù)據(jù)插入測試表中。DbUnit 可以有不同的數(shù)據(jù)庫操作,我使用了其中的兩種:
DELETE_ALL ,它刪除表中所有行。
CLEAN_INSERT ,它刪除表中所有行并插入數(shù)據(jù)集提供的行。
ProjectDatabaseTestCase 中的下面四個(gè)方法可以滿足您的需要:
insertFileIntoDb() :在數(shù)據(jù)庫中插入文件。
emptyTable() :清理數(shù)據(jù)庫表。
insertAllFilesIntoDb() :插入項(xiàng)目的所有文件。
emptyAllTables() :清理項(xiàng)目的所有表。
完成了基類后,用 DbUnit 干凈地建立數(shù)據(jù)庫,執(zhí)行一個(gè)方法,并檢查返回值是很容易的事。
在這個(gè)測試中,我清空了數(shù)據(jù)庫,插入一個(gè)表的內(nèi)容,并通過檢查它返回的元素是否有正確的屬性來檢查用主鍵查找產(chǎn)品的 finder 方法是否正常工作。然后測試對象創(chuàng)建工作,并用 DbUnit 的查詢程序驗(yàn)證數(shù)據(jù)庫的內(nèi)容。
需要注意的一件重要事情是,清理數(shù)據(jù)庫是在建立測試而不是結(jié)束時(shí)進(jìn)行的。我不想依賴于每次測試都干凈地結(jié)束。
在插入數(shù)據(jù)時(shí)要關(guān)注的事情
數(shù)據(jù)庫完整性約束迫使您以給定的順序插入或者刪除數(shù)據(jù)。在編寫 insertAllFiles() 和 emptyAllTables() 方法時(shí),您會發(fā)現(xiàn)順序并非是隨意的,事實(shí)上它是由完整性約束所限定的。
另一個(gè)潛在的陷井是,一些列可能看來沒有插入。幾乎總是會出現(xiàn)這種情況,因?yàn)樵?FlatXmlDataSet 中的第一行缺少一列。看來 DbUnit 不能識別所有其他行中的這一列。
總是要保證第一行描述包含表中的所有列。如果需要插入一個(gè) NULL 值,要使這一行成為第二行。
組織測試數(shù)據(jù)
DbUnit 可以在文件中存儲 XML 數(shù)據(jù)集。它甚至允許在一個(gè)文件中存儲整個(gè)數(shù)據(jù)庫。
決定如何存儲測試數(shù)據(jù)很重要。是將每一個(gè)表的內(nèi)容存儲到單獨(dú)的文件中,還是將與系統(tǒng)主要實(shí)體有關(guān)的所有表的所有行存儲到一個(gè)文件中?它們都不是完美的解決方案(silver bullet)。
對于第一種情況,保證跨表的數(shù)據(jù)一致更困難,但是用一個(gè)已經(jīng)存在的數(shù)據(jù)庫創(chuàng)建查詢更容易。在第二種情況下,為每一個(gè)測試創(chuàng)建測試集更容易,但是事實(shí)上大多數(shù)系統(tǒng)不是圍繞一個(gè)主要實(shí)體設(shè)計(jì)的,因此這使它不那么實(shí)用。我們的方法是每個(gè)表有一個(gè)文件。
用 Anthill 實(shí)現(xiàn)持續(xù)集成
Anthill 是一個(gè)免費(fèi)的自動(dòng)構(gòu)建工具(請參閱 參考資料),它規(guī)劃您的構(gòu)建并發(fā)布結(jié)果,幫助精通 XP 的小組使用持續(xù)集成。一次構(gòu)建包含用 CVS 這樣的版本控制工具檢查源代碼、運(yùn)行一個(gè)構(gòu)建腳本、發(fā)布結(jié)果并通知用戶結(jié)果。它很好地與 ANT 集成,使您可以重用常用的構(gòu)建腳本。
在 Anthill 中運(yùn)行測試包并報(bào)告結(jié)果
XP 專家一直建議將持續(xù)集成作為確保減少集成錯(cuò)誤一種方式:通過以足夠高的頻率集成所有代碼,保證容易追溯到源代碼中的問題。集成可能是非常耗時(shí)的任務(wù)--檢查、構(gòu)建和部署代碼,然后運(yùn)行驗(yàn)收試驗(yàn)。幸運(yùn)的是,其中大多數(shù)可以用 Anthill 或者 CruiseControl 這樣的工具自動(dòng)化。如果還沒有使構(gòu)建過程自動(dòng)化(例如用 Ant),那您應(yīng)當(dāng)這樣做。如果構(gòu)建過程是自動(dòng)化的,應(yīng)當(dāng)在構(gòu)建中加入一個(gè)測試部分。如果您是頑固的 XP 用戶,這些應(yīng)當(dāng)是您的驗(yàn)收測試。如果您像我們一樣,那么這些是您要編寫的所有測試--不管是單元、驗(yàn)收或者其他測試。
我們的構(gòu)建過程基于 Ant 并計(jì)劃使用 Anthill。我們的主要挑戰(zhàn)是讓 Anthill 報(bào)告失敗的測試并且仍然發(fā)布測試結(jié)果。Anthill 捕獲的是:如果構(gòu)建腳本失敗,不執(zhí)行發(fā)布腳本,在這種情況下不能將測試報(bào)告提供給開發(fā)人員。我們的方法是讓 Anthill 檢查屬性為 true 還是 false,而使它在發(fā)布腳本的后才失敗。
運(yùn)行測試的目標(biāo)
下面是關(guān)于運(yùn)行測試的簡要總結(jié)。我們使用的是批量化的方法,但是任何方法都可以工作。要點(diǎn)有:
測試必須具有分支,以便在類路徑包含 JDK 1.3 中的 XML 解析器時(shí)可以正常工作。
如果出現(xiàn)錯(cuò)誤或者失敗,則 testsuite.error 和 testsuite.failure 屬性必須設(shè)置為 true。如果沒有錯(cuò)誤或者失敗的話,則不改變它們。
一個(gè)需要了解的重要的 Ant 技巧是,Ant 只在屬性沒有值時(shí)才設(shè)置屬性的值。所以在依次運(yùn)行每一個(gè)測試時(shí), testsuite.error 和 testsuite.failure 屬性只有當(dāng)出現(xiàn)錯(cuò)誤或者失敗時(shí)才會是 true。
這里的困難是向主 Ant 腳本報(bào)告測試腳本的結(jié)果。不幸的是,這并不是一項(xiàng)簡單的任務(wù),因?yàn)樵?Anthill 的過程中有兩個(gè)不同的 Ant 構(gòu)建文件,在 Ant 中不能在構(gòu)建腳本之間傳遞這種參數(shù)。不過,有一個(gè)“簡單”的解決方案:將測試的結(jié)果保存到文件中,之后發(fā)布腳本讀取這個(gè)文件。
使用這種 Ant 技巧,它顯示了如何使用 <property> 命令保證 testsuite.error 和 testsuite.failure 屬性在測試腳本結(jié)束時(shí)總是有一個(gè)值,以及如何將它保存為文件。
如果測試失敗,使發(fā)布腳本在結(jié)束時(shí)失敗。
結(jié)束語
我們的小組成功地在 2003 年初引入了 DbUnit 和 Anthill。從那以后,我們編寫并自動(dòng)化了上千次測試--其中 75% 涉及設(shè)置數(shù)據(jù)庫狀態(tài)。我們每小時(shí)運(yùn)行一次測試,并計(jì)劃很快以更短的周期運(yùn)行它們。它們捕獲了很多未預(yù)料到的缺陷,這使它們成為不可缺少的工具。