極限編程方法的興起將測(cè)試驅(qū)動(dòng)開(kāi)發(fā)和持續(xù)集成帶入了主流 Java 開(kāi)發(fā)實(shí)踐。如果沒(méi)有采用正確的工具,在 Java 服務(wù)器端開(kāi)發(fā)中使用這些技術(shù)很快會(huì)成為一場(chǎng)噩夢(mèng)。在本文中,軟件開(kāi)發(fā)人員 Philippe Girolami 描述了如何處理持續(xù)集成,以及如何聯(lián)合使用 DbUnit 和 JUnit,以便在每次測(cè)試之前通過(guò)設(shè)置數(shù)據(jù)庫(kù)狀態(tài)來(lái)端到端地控制測(cè)試環(huán)境。
軟件開(kāi)發(fā)中重要的一種做法是測(cè)試。通過(guò)推薦測(cè)試優(yōu)先的開(kāi)發(fā)和持續(xù)集成,極限編程(Extreme Programming,XP)將這一邏輯推到了極限,在這里測(cè)試是盡可能頻繁地自動(dòng)進(jìn)行的。不過(guò),大多數(shù)非 XP 開(kāi)發(fā)都進(jìn)行了某種形式的測(cè)試,也許稱為非回歸測(cè)試、黑箱測(cè)試、功能測(cè)試或者其他的名字。很多項(xiàng)目使用關(guān)系數(shù)據(jù)庫(kù)存儲(chǔ)數(shù)據(jù),因而所有測(cè)試策略都需要考慮在每次測(cè)試過(guò)程中數(shù)據(jù)庫(kù)中所發(fā)生的事情:如果測(cè)試使測(cè)試數(shù)據(jù)庫(kù)處于不一致?tīng)顟B(tài),那么后面的所有測(cè)試都可能失!一種避免這種情況的方法是在每次測(cè)試之前將數(shù)據(jù)庫(kù)狀態(tài)設(shè)為一個(gè)已知的相關(guān)狀態(tài)。在本文中,我將介紹我們的小組是如何結(jié)合 JUnit 使用 DbUnit 做到這一點(diǎn)的,以及如何用 Anthill 自動(dòng)生成測(cè)試報(bào)告。盡管設(shè)置看起來(lái)很費(fèi)功夫,但是實(shí)際上并不是這樣,并且它已經(jīng)證明自己是一個(gè)有用的工具。
表示數(shù)據(jù)庫(kù)內(nèi)容
DbUnit 擴(kuò)展了 JUnit,它使數(shù)據(jù)庫(kù)在測(cè)試之間處于一種已知狀態(tài),幫助避免造成后面的測(cè)試失敗或者給出錯(cuò)誤結(jié)果的問(wèn)題,如果測(cè)試會(huì)破壞數(shù)據(jù)庫(kù)會(huì)出現(xiàn)這些問(wèn)題。它可以讀取表的內(nèi)容并用 FlatXmlDataSet 將它在存儲(chǔ)為 XML,如清單 1 所示:
清單 1. FlatXmlDataSet 示例
<dataset> <OPERATOR ID='APC (Washington/Baltimore)' CODE='ABC5APC' ENCODED_STRING='aabbclearcase/" target="_blank" >cc'/> <OPERATOR ID='ASA Ritabell' CODE='ABC6ASA R' ENCODED_STRING='bbccdd'/> <OPERATOR ID='Advanced Info. Service PLC' CODE='ABC1Adva' ENCODED_STRING='ccddee'/> <OPE_OPERATOR ID='Aerial Communications Inc.' CODE='ABC2Aeri' ENCODED_STRING='ddeeff'/></dataset>
這個(gè)數(shù)據(jù)集表示名為 OPE_OPERATOR 的數(shù)據(jù)庫(kù)表中的三列,如表 1 中后三行所描述的:
表 1.中數(shù)據(jù)的表定義
OPE_OPERATOR | ||
ID | INT | |
CODE | VARCHAR | |
ENCODED_STRING | VARCHAR |
每個(gè) XML 實(shí)體標(biāo)識(shí)數(shù)據(jù)庫(kù)中的一個(gè)表,而每個(gè)屬性表示一列的值。
在自己的項(xiàng)目中設(shè)置 DbUnit
設(shè)置 DbUnit 很簡(jiǎn)單。有關(guān)項(xiàng)目文件下載的信息,請(qǐng)參閱 參考資料的內(nèi)容?梢詫⑺腥齻(gè) JAR 文件加到項(xiàng)目的編譯目標(biāo)中以進(jìn)行測(cè)試。
如果是一個(gè)多 schema 環(huán)境,那么要將 DbUnit.qualified.table.names 屬性設(shè)置為 true 。使用 Oracle 的開(kāi)發(fā)團(tuán)隊(duì)通常是這種情況:每一個(gè)用戶有自己的 schema。這可以使您免于在每個(gè)表名前面加上 schema 名稱的前綴,并可以在團(tuán)隊(duì)中共享測(cè)試數(shù)據(jù)。
DbUnit 使您可以容易地執(zhí)行 JDBC 查詢并獲取它們的值。使用 DbUnit JDBC 包裝器而不是純粹的 JDBC 有幾個(gè)理由:
可以用 SQL 查詢創(chuàng)建一個(gè) Dataset ,并使用 DbUnit 的 assertion(斷言)方法(在后面描述)。
可以用 SQL 查詢創(chuàng)建一個(gè) Dataset ,并將它保存為一個(gè) FlatXmlDataSet ?梢栽谝院髮⑺匦卵b載到數(shù)據(jù)庫(kù)中。
可以容易地從任何行中獲取列的內(nèi)容,無(wú)需進(jìn)行迭代。
首先檢查行計(jì)數(shù)是否為 1,然后檢查第一行(從 0 開(kāi)始計(jì))中, FK_OTHER_ID 列包含數(shù)字 1234。
使用 assert 方法檢查數(shù)據(jù)庫(kù)內(nèi)容
DbUnit 有斷言方法,可以用于比較表的兩組數(shù)據(jù)或者表的兩個(gè)表示。如果需要在運(yùn)行一次測(cè)試而不是多次查詢后檢查表的確切內(nèi)容,一般會(huì)用它們。
創(chuàng)建數(shù)據(jù)
根據(jù)數(shù)據(jù)庫(kù)的大小、架構(gòu)的穩(wěn)定性如何以及開(kāi)發(fā)的進(jìn)展情況,可能要從頭開(kāi)始創(chuàng)建或者從生產(chǎn)數(shù)據(jù)庫(kù)中拷貝測(cè)試數(shù)據(jù)。
如果導(dǎo)出一個(gè)完整的生產(chǎn)數(shù)據(jù)庫(kù),可能必須要?jiǎng)h除過(guò)多的行--或者像在這里一樣,用一個(gè)查詢而不是直接用連接創(chuàng)建一個(gè)數(shù)據(jù)集。提取本身對(duì)于很大的表來(lái)說(shuō)可能是個(gè)問(wèn)題--我們的小組只能用查詢提取某些表的一部分。從表中刪除行也有些問(wèn)題,主要涉及到瀏覽所有外鍵并保證數(shù)據(jù)的一致性的困難。
添加測(cè)試數(shù)據(jù)
添加測(cè)試數(shù)據(jù)有時(shí)可能乏味的。我們的經(jīng)驗(yàn)是度過(guò)正確添加數(shù)據(jù)的初困難階段后,可以達(dá)到這樣一個(gè)層次,不僅添加數(shù)據(jù)變得容易了,而且對(duì)數(shù)據(jù)庫(kù)結(jié)構(gòu)的理解也有了極大提高。
即使使用 Enterprise JavaBeans (EJB) 技術(shù)隱藏?cái)?shù)據(jù)庫(kù),這種第一手知識(shí)仍然非常有用。因?yàn)殚_(kāi)發(fā)人員對(duì)數(shù)據(jù)庫(kù)有了更好的理解,因而可以更快地檢查其內(nèi)容,從而使調(diào)試更容易了。這在重構(gòu)代碼和數(shù)據(jù)庫(kù)時(shí)又會(huì)給予我們極大的幫助。
用 DbUnit 和 JUnit 創(chuàng)建基類
好的 JUnit 實(shí)踐鼓勵(lì)開(kāi)發(fā)人員擴(kuò)展基類 TestCase 以獲得特化(specialization)行為。DbUnit 提供了自己的特化-- DatabaseTestCase ,通過(guò)它可以特化行為以滿足自己的需要。
首先,創(chuàng)建一個(gè)名為 ProjectDatabaseTestCase 的基本測(cè)試用例,并向它添加實(shí)用工具方法,然后,重新定義 setUp() 和 teardown() ,以使它們能夠創(chuàng)建和銷毀通過(guò) DbUnit 到數(shù)據(jù)庫(kù)的連接。