不久以前,developerWorks 的作者 Andrew Glover 撰寫(xiě)了一篇介紹 Groovy 的文章,該文章是 alt.lang.jre 系列的一部分,而 Groovy 是一個(gè)新提議的用于 Java 平臺(tái)的標(biāo)準(zhǔn)語(yǔ)言。讀者對(duì)這篇文章的反應(yīng)非常熱烈,所以我們決定開(kāi)辦這個(gè)專欄,提供使用這項(xiàng)熱門(mén)新技術(shù)的實(shí)用指導(dǎo)。本文是第一期,將介紹使用 Groovy 和 JUnit 對(duì) Java 代碼進(jìn)行單元測(cè)試的一個(gè)簡(jiǎn)單策略。
開(kāi)始之前,我首先要招認(rèn):我是一個(gè)單元測(cè)試狂。實(shí)際上,我總是無(wú)法編寫(xiě)足夠的單元測(cè)試。如果我相當(dāng)長(zhǎng)一段時(shí)間都在進(jìn)行開(kāi)發(fā),而 沒(méi)有編寫(xiě)相應(yīng)的單元測(cè)試,我就會(huì)覺(jué)得緊張。單元測(cè)試給我信心,讓我相信我的代碼能夠工作,而且我只要看一下,可以修改它,就不會(huì)害怕它會(huì)崩潰。
而且,作為一個(gè)單元測(cè)試狂,我喜歡編寫(xiě)多余的測(cè)試用例。但是,我的興奮不是來(lái)自 編寫(xiě)測(cè)試用例,而是 看著它們生效。所以,如果我能用更快的方式編寫(xiě)測(cè)試,我就能更迅速地看到它們的結(jié)果。這讓我感覺(jué)更好。更快一些!
后來(lái),我找到了 Groovy,它滿足了我的單元測(cè)試狂,而且至今為止,對(duì)我仍然有效。這種新語(yǔ)言給單元測(cè)試帶來(lái)的靈活性,非常令人興奮,值得認(rèn)真研究。本文是介紹 Groovy 實(shí)踐方面的新系列的第一部分,在文中,我將向您介紹使用 Groovy 進(jìn)行單元測(cè)試的快樂(lè)。我從概述開(kāi)始,概述 Groovy 對(duì) Java 平臺(tái)上的開(kāi)發(fā)所做的獨(dú)特貢獻(xiàn),然后轉(zhuǎn)而討論使用 Groovy 和 JUnit 進(jìn)行單元測(cè)試的細(xì)節(jié),其中重點(diǎn)放在 Groovy 對(duì) JUnit 的 TestCase
類(lèi)的擴(kuò)展上。最后,我用一個(gè)實(shí)用的示例進(jìn)行總結(jié),用第一手材料向您展示如何把 groovy 的這些特性與 Eclipse 和 Maven 集成在一起。
不要再堅(jiān)持 Java 純粹主義了!
在我開(kāi)始介紹用 Groovy 進(jìn)行單元測(cè)試的實(shí)際經(jīng)驗(yàn)之前,我認(rèn)為先談?wù)勔粋€(gè)更具一般性的問(wèn)題 —— 它在您的開(kāi)發(fā)工具箱中的位置,這非常重要。事實(shí)是,Groovy 不僅是運(yùn)行在 Java 運(yùn)行時(shí)環(huán)境(JRE)中的腳本語(yǔ)言,它還被提議作為用于 Java 平臺(tái)的標(biāo)準(zhǔn)語(yǔ)言。正如您們之中的人已經(jīng)從 alt.lang.jre 系列(請(qǐng)參閱 參考資料)中學(xué)習(xí)到的,在為 Java 平臺(tái)進(jìn)行腳本編程的時(shí)候,有無(wú)數(shù)的選擇,其中大多數(shù)是面向快速應(yīng)用程序開(kāi)發(fā)的高度靈活的環(huán)境。
雖然有這么豐富的選擇,但還是有許多開(kāi)發(fā)人選擇堅(jiān)持自己喜歡的、最熟悉的范式:Java 語(yǔ)言。雖然大多數(shù)情況下,Java 編程都是很好的選擇,但是它有一個(gè)非常重要的缺點(diǎn),蒙住了只看見(jiàn) Java 的好處的這些人的眼睛。正如一個(gè)智者曾經(jīng)指出的: 如果您僅有的一個(gè)工具是一把錘子,那么您看每個(gè)問(wèn)題時(shí)都會(huì)覺(jué)得它像是釘子。我認(rèn)為這句諺語(yǔ)道出了適用于軟件開(kāi)發(fā)的許多事實(shí)。
雖然我希望用這個(gè)系列說(shuō)服您 Java 不是也不應(yīng)當(dāng)是開(kāi)發(fā)應(yīng)用程序的惟一選擇,但該腳本確實(shí)既有適用的地方也有不適用的地方。專家和新手的區(qū)別在于:知道什么時(shí)候 運(yùn)用該腳本,什么時(shí)候 避免使用它。
關(guān)于本系列 |
例如,對(duì)于高性能、事務(wù)密集型、企業(yè)級(jí)應(yīng)用程序,Groovy 腳本通常不太適合;在這些情況下,您最好的選擇 可能是普通的 J2EE 系統(tǒng)。但另一方面,一些腳本 —— 特別是用 Groovy 編寫(xiě)的腳本 —— 會(huì)非常有用,因?yàn)樗苎杆俚貫樾⌒偷摹⒎浅L厥獾摹⒉皇切阅苊芗偷膽?yīng)用程序(例如配置系統(tǒng)/生成系統(tǒng))快速制作原型。對(duì)于報(bào)表應(yīng)用程序來(lái)說(shuō), Groovy 腳本也是近乎完美的選擇,而最重要的是,對(duì)單元測(cè)試更是如此。
為什么用 Groovy 進(jìn)行單元測(cè)試?
是什么讓 Groovy 比起其他腳本平臺(tái)顯得更具有吸引力呢?是它與Java 平臺(tái)無(wú)縫的集成。還是因?yàn)樗腔?Java 的語(yǔ)言(不像其他語(yǔ)言,是對(duì) JRE 的替代,因此可能基于舊版的處理器),對(duì)于 Java 開(kāi)發(fā)人員來(lái)說(shuō),Groovy 意味著一條短得讓人難以置信的學(xué)習(xí)曲線。而且一旦將這條學(xué)習(xí)曲線拉直,Groovy 就能提供一個(gè)無(wú)與倫比的快速開(kāi)發(fā)平臺(tái)。
從這個(gè)角度來(lái)說(shuō),Groovy 成功的秘密,在于它的語(yǔ)法 就是 Java 語(yǔ)法,但是規(guī)則更少。例如,Groovy 不要求使用分號(hào),變量類(lèi)型和訪問(wèn)修飾符也是可選的。而且,Groovy 利用了標(biāo)準(zhǔn)的 Java 庫(kù),這些都是您已經(jīng)很熟悉的,包括 Collections
和 File/IO
。而且,您還可以利用任何 Groovy 提供的 Java 庫(kù),包括 JUnit。
事實(shí)上,令人放松的類(lèi) Java 語(yǔ)法、對(duì)標(biāo)準(zhǔn) Java 庫(kù)的重用以及快捷的生成-運(yùn)行周期,這些都使 Groovy 成為快速開(kāi)發(fā)單元測(cè)試的理想替代品。但是會(huì)說(shuō)的不如會(huì)做的,還是讓我們?cè)诖a中看看它的實(shí)際效果!
JUnit 和 Groovy
用 Groovy 對(duì) Java 代碼進(jìn)行單元測(cè)試簡(jiǎn)單得不能再簡(jiǎn)單了,有很多入門(mén)選擇。最直接的選擇是沿襲行業(yè)標(biāo)準(zhǔn) —— JUnit。Unit 的簡(jiǎn)單性和其功能的強(qiáng)大都是無(wú)與倫比的,作為非常有幫助的 Java 開(kāi)發(fā)工具,它的普遍性也是無(wú)與倫比的,而且沒(méi)有什么能夠阻擋 JUnit 和 Groovy 結(jié)合,所以為什么多此一舉呢?實(shí)際上,只要您看過(guò) JUnit 和 Groovy 在一起工作,我敢打賭您就永遠(yuǎn)再也不會(huì)回頭!在這里,要記住的關(guān)鍵的事,您在 Java 語(yǔ)言中能用 JUnit 做到的事,在 Groovy 中用 JUnit 也全都能做到;但是需要的代碼要少得多。
入門(mén)
在您下載了 JUnit 和 Groovy(請(qǐng)參閱 參考資料)之后,您將有兩個(gè)選擇。第一個(gè)選擇是編寫(xiě)普通的 JUnit 測(cè)試用例,就像以前一直做的那樣,擴(kuò)展 JUnit 令人稱贊的 TestCase
。第二個(gè)選擇是應(yīng)用 Groovy 簡(jiǎn)潔的 GroovyTestCase
擴(kuò)展,它會(huì)擴(kuò)展 JUnit 的 TestCase
。第一個(gè)選項(xiàng)是您最快的成功途徑,它擁有最多 與 Java 類(lèi)似的相似性。而另一方面,第二個(gè)選擇則把您推進(jìn)了 Groovey 的世界,這個(gè)選擇有最大的敏捷性。
開(kāi)始的時(shí)候,我們來(lái)想像一個(gè) Java 對(duì)象,該對(duì)象對(duì)指定的 string
應(yīng)用了一個(gè)過(guò)濾器,并根據(jù)匹配結(jié)果返回 boolean
值。該過(guò)濾器可以是簡(jiǎn)單的 string
操作,例如 indexOf()
,也可以更強(qiáng)大一些,是正則表達(dá)式。可能要通過(guò) setFilter()
方法在運(yùn)行時(shí)設(shè)置將使用的過(guò)濾器, apply()
方法接受要過(guò)濾的 string
。清單 1 用普通的 Java 代碼顯示了這個(gè)示例的 Filter
接口:
|
我們的想法是用這個(gè)特性從大的列表中過(guò)濾掉想要的或者不想要的包名。所以,我建立了兩個(gè)實(shí)現(xiàn): RegexPackageFilter
和 SimplePackageFilter
。
把 Groovy 和 JUnit 的強(qiáng)大功能與簡(jiǎn)單性結(jié)合在一起,就形成了如清單 2 所示的簡(jiǎn)潔的測(cè)試套件:
清單 2. 用 JUunit 制作的 Groovy RegexFilterTest
|
不管您是否熟悉 Groovy,清單 2 中的代碼對(duì)您來(lái)說(shuō)應(yīng)當(dāng)很面熟,因?yàn)樗徊贿^(guò)是沒(méi)有分號(hào)、訪問(wèn)修飾符或變量類(lèi)型的 Java 代碼而已!上面的 JUnit 測(cè)試中有一個(gè)測(cè)試用例 testSimpleRegex()
,它試圖斷言 RegexPackageFilter
用正則表達(dá)式 "java.*"
正確地找到了與 “ java.lang.String
”匹配的對(duì)象。
Groovy 擴(kuò)展了 JUnit
擴(kuò)展 JUnit 的 TestCase
類(lèi),加入附加特性,實(shí)際上是每個(gè) JUnit 擴(kuò)展通常采用的技術(shù)。例如,DbUnit 框架(請(qǐng)參閱 參考資料)提供了一個(gè)方便的 DatabaseTestCase
類(lèi),能夠比以往任何時(shí)候都更容易地管理數(shù)據(jù)庫(kù)的狀態(tài),還有重要的 MockStrutsTestCase
(來(lái)自 StrutsTestCase 框架;請(qǐng)參閱 參考資料),它生成虛擬的 servlet
容器,用來(lái)執(zhí)行 struts 代碼。這兩個(gè)強(qiáng)大的框架都極好地?cái)U(kuò)展了 JUnit,提供了 JUnit 核心代碼中所沒(méi)有的其他特性;而現(xiàn)在 Groovy 來(lái)了,它也是這么做的!
與 StrutsTestCase 和 DbUnit 一樣,Groovy 對(duì) JUnit 的 TestCase
的擴(kuò)展給您的工具箱帶來(lái)了一些重要的新特性。這個(gè)特殊的擴(kuò)展允許您通過(guò) groovy
命令運(yùn)行測(cè)試套件,而且提供了一套新的 assert
方法。可以用這些方法很方便地?cái)嘌阅_本的運(yùn)行是否正確,以及斷言各種數(shù)組類(lèi)型的長(zhǎng)度和內(nèi)容等。
享受 GroovyTestCase 的快樂(lè)
了解 GroovyTestCase
的能力最好的辦法,莫過(guò)于實(shí)際看到它的效果。在清單 3 中,我已經(jīng)編寫(xiě)了一個(gè)新的 SimpleFilterTest
,但是這次我要擴(kuò)展 GroovyTestCase
來(lái)實(shí)現(xiàn)它:
|
請(qǐng)注意,可以通過(guò)命令行來(lái)運(yùn)行該測(cè)試套件,沒(méi)有運(yùn)行基于 Java 的 JUnit 測(cè)試套件所需要的 main()
方法。實(shí)際上,如果我用 Java 代碼編寫(xiě)上面的 SimpleFilterTest
,那么代碼看起來(lái)會(huì)像清單 4 所示的那樣:
|
用斷言進(jìn)行測(cè)試
除了可以讓您通過(guò)命令行運(yùn)行測(cè)試之外, GroovyTestCase
還向您提供了一些特別方便的 assert
方法。例如, assertArrayEquals
,它可以檢查兩個(gè)數(shù)據(jù)中對(duì)應(yīng)的每一個(gè)值和各自的長(zhǎng)度,從而斷言這兩個(gè)數(shù)據(jù)是否相等。從清單 5 的示例開(kāi)始,就可以看到 Groovy 斷言的實(shí)際效果,清單 5 是一個(gè)簡(jiǎn)潔的基于 Java 的方法,它把 string
分解成數(shù)組。(請(qǐng)注意,我可能使用了 Java 1.4 中新添加的 string
特性編寫(xiě)以下的示例類(lèi)。我采用 Jakarta Commons StringUtils
類(lèi)來(lái)確保與 Java 1.3 的后向兼容性。)
|
清單 6 展示了用 Groovy 測(cè)試套件及其對(duì)應(yīng)的 assertArrayEquals
方法對(duì)這個(gè)類(lèi)進(jìn)行測(cè)試是多么簡(jiǎn)單:
|
其他方法
Groovy 可以讓您單獨(dú)或成批運(yùn)行測(cè)試。使用 GroovyTestCase
擴(kuò)展,運(yùn)行單個(gè)測(cè)試毫不費(fèi)力。只要運(yùn)行 groovy
命令,后面跟著要運(yùn)行的測(cè)試套件即可,如清單 7 所示:
|
Groovy 還提供了一個(gè)標(biāo)準(zhǔn)的 JUnit 測(cè)試套件,叫作 GroovyTestSuite
。只要運(yùn)行該測(cè)試套件,把腳本的路徑傳給它,它就會(huì)運(yùn)行腳本,就像 groovy
命令一樣。這項(xiàng)技術(shù)的好處是,它可以讓您在 IDE 中運(yùn)行腳本。例如,在 Eclipse 中,我只是為示例項(xiàng)目建立了一個(gè)新的運(yùn)行配置(一定要選中 “Include external jars when searching for a main class”),然后找到主類(lèi) groovy.util.GroovyTestSuite
,如圖 1 所示:
圖 1. 用 Eclipse 運(yùn)行 GroovyTestSuite
在圖 2 中,您可以看到當(dāng)點(diǎn)擊 Arguments 標(biāo)簽,寫(xiě)入腳本的路徑時(shí),會(huì)發(fā)生了什么:
運(yùn)行一個(gè)自己喜歡的 JUnit Groovy 腳本,實(shí)在是很簡(jiǎn)單,只要在 Eclipse 中找到對(duì)應(yīng)的運(yùn)行配置就可以了。
用 Ant 和 Maven 進(jìn)行測(cè)試
這個(gè)像 JUnit 一樣的框架的美妙之處還在于,它可以把整套測(cè)試作為 build 的一部分運(yùn)行,不需要人工進(jìn)行干預(yù)。隨著越來(lái)越多的人把測(cè)試用例加入代碼基(code base),整體的測(cè)試套件日益增長(zhǎng),形成一個(gè)極好的回歸平臺(tái)(regression platform)。更妙的是,Ant 和 Maven 這樣的 build 框架已經(jīng)加入了報(bào)告特性,可以歸納 Junit 批處理任務(wù)運(yùn)行的結(jié)果。
把一組 Groovy 測(cè)試用例整合到某一個(gè)構(gòu)建中的最簡(jiǎn)單的方法是把它們編譯成普通的 Java 字節(jié)碼,然后把它們包含在 Ant 和 Maven 提供的標(biāo)準(zhǔn)的 Junit 批命令中。幸運(yùn)的是,Groovy 提供了一個(gè) Ant 標(biāo)簽,能夠把未編譯的 Groovy 腳本集成到字節(jié)碼中,這樣,把腳本轉(zhuǎn)換成有用的字節(jié)碼的處理工作就變得再簡(jiǎn)單不過(guò)。例如,如果正在使用 Maven 進(jìn)行構(gòu)建工作,那么只需在maven.xml 文件中添加兩個(gè)新的目標(biāo)、在 project.xml 中添加兩個(gè)新的相關(guān)性、在 build.properties 文件中添加一個(gè)簡(jiǎn)單的標(biāo)志就可以了。
我要從更新 maven.xml 文件開(kāi)始,用新的目標(biāo)來(lái)編譯示例腳本,如清單 8 所示:
清單 8. 定義 Groovyc 目標(biāo)的新 maven.xml 文件
|
上面代碼中發(fā)生了以下幾件事。第一,我定義一個(gè)名為 run-groovyc
的新目標(biāo)。該目標(biāo)有兩個(gè)前提條件, java:compile
編譯示例源代碼, test:compile
編譯普通的 Java-JUnit 類(lèi)。我還用 <path>
標(biāo)簽創(chuàng)建了一個(gè) classpath。在該例中,classpath 把 build 目錄(保存編譯后的源文件)和與它相關(guān)的所有依存關(guān)系(即 JAR 文件)整合在一起。接著,我還用 <taskdef>
Ant 標(biāo)簽定義了 groovyc
任務(wù)。
而且,請(qǐng)您注意我在 classpath 中是如何告訴 Maven 到哪里去找 org.codehaus.groovy.ant.Groovyc
這個(gè)類(lèi)。在示例的最后一行,我定義了 <groovyc>
標(biāo)簽,它會(huì)編譯在 test/groovy
目錄中發(fā)現(xiàn)的全部 Groovy 腳本,并把生成的 .class 文件放在 target/test-classes
目錄中。
一些重要細(xì)節(jié)
為了編譯 Groovy 腳本,并運(yùn)行生成的字節(jié)碼,我必須要通過(guò) project.xml 文件定義兩個(gè)新的依存關(guān)系( groovy 和 asm),如清單 9 所示:
|
一旦將腳本編譯成普遍的 Java 字節(jié)碼,那么任何標(biāo)準(zhǔn)的 JUnit 運(yùn)行器就都能運(yùn)行它們。因?yàn)?Ant 和 Maven 都擁有 JUnit 運(yùn)行器標(biāo)簽,所以下一步就是讓 JUnit 挑選新編譯的 Groovy 腳本。而且,因?yàn)?Maven 的 JUnit 運(yùn)行器使用模式匹配來(lái)查找要運(yùn)行的測(cè)試套件,所以需要在 build.properties 文件中添加一個(gè)特殊標(biāo)記,如清單 10 所示,該標(biāo)記告訴 Maven 去搜索類(lèi)而不是搜索 .java 文件。
清單 10. Maven 項(xiàng)目的 build.properties 文件
|
最后,我在 maven.xml 文件中定義了一個(gè)測(cè)試目標(biāo)( goal
),如清單 11 所示。這樣做可以確保 在單元測(cè)試運(yùn)行之前,使用新的 run-groovyc
目標(biāo)編譯 Groovy 腳本。
|
最后一個(gè),但并非最不重要
有了新定義的兩個(gè)目標(biāo)(一個(gè)用來(lái)編譯腳本,另外一個(gè)用來(lái)運(yùn)行 Java 和 Groovy 組合而成的單元測(cè)試),剩下的事就只有運(yùn)行它們,檢查是不是每件事都順利運(yùn)行!
在清單 12 中,您可以看到,當(dāng)我運(yùn)行 Maven,給 test
傳遞目標(biāo)之后,會(huì)發(fā)生了什么,它首先包含 run-groovyc
目標(biāo)(而該目標(biāo)恰好還包含 java:compile
和 test:compile
這兩個(gè)目標(biāo)),然后包含 Maven 中自帶的標(biāo)準(zhǔn)的 test:test
目標(biāo)。請(qǐng)注意觀察目標(biāo) test:test
是如何處理新生成的 Groovy 腳本(在該例中,是新 編譯的 Groovy 腳本) 以及普通的 Java JUnit 測(cè)試。
|
結(jié)束語(yǔ)
在 實(shí)戰(zhàn) Groovy 系列的第一期中,您學(xué)習(xí)了 Groovy 這個(gè)令人興奮的腳本語(yǔ)言最實(shí)用的應(yīng)用當(dāng)中的一個(gè)。對(duì)于越來(lái)越多開(kāi)發(fā)人員而言,單元測(cè)試是開(kāi)發(fā)過(guò)程的重要組成部分;而使用 Groovy 和 JUnit 對(duì) Java 代碼進(jìn)行測(cè)試就變成了輕而易舉的事情。
Groovy 簡(jiǎn)單的語(yǔ)法、內(nèi)部的靈活性,使其成為迅速編寫(xiě)有效的 JUnit 測(cè)試、將測(cè)試整合到自動(dòng)編譯中的一個(gè)優(yōu)秀平臺(tái)。對(duì)于像我一樣為代碼質(zhì)量發(fā)狂的人來(lái)說(shuō),這種組合極大地減少了我的 神經(jīng)緊張,還讓我可以得到我想做得最好的東西:編寫(xiě)“防彈”軟件。快點(diǎn)行動(dòng)吧。
因?yàn)檫@是一個(gè)新的系列,所以我非常希望您能一起來(lái)推動(dòng)它前進(jìn)。如果您對(duì) Groovy 有什么想了解的事情,請(qǐng) 發(fā)郵件給我,讓我知道您的要求!我希望您會(huì)繼續(xù)支持本系列的下一期,在下期中,我將介紹用 Groovy 進(jìn)行 Ant 腳本編程。