Jython開發(fā)的JUnit測(cè)試包
JUnit 測(cè)試框架被越來(lái)越多的開發(fā)小組所共同使用。歸功于各種各樣的測(cè)試裝具模塊,現(xiàn)在可以測(cè)試構(gòu)成任何 Java 應(yīng)用程序的幾乎每一個(gè)組件。事實(shí)上,幾乎整個(gè)二級(jí)市場(chǎng)似乎都是用圍繞 Junit 建立的。包括 Cactus、jfcUnit、XMLUnit、DbUnit 和 HttpUnit 這樣的裝具模塊都可以免費(fèi)供開發(fā)人員用于測(cè)試應(yīng)用程序。隨著系統(tǒng)的復(fù)雜程度的增加,并且有這么多工具可供使用,沒有什么理由不依靠單元測(cè)試。
不過(guò),開發(fā)人員不僅僅是程序員。我們與用戶交互以修復(fù) bug 并確定需求。我們參加會(huì)議并進(jìn)行電話推銷。我們完成一些(有時(shí)全部)質(zhì)量保證功能。既然有這么多責(zé)任,希望盡可能自動(dòng)化就是自然而然的了。因?yàn)楹玫膱F(tuán)隊(duì)(除了其他事情外)會(huì)進(jìn)行大量測(cè)試,希望自動(dòng)化不同的開發(fā)過(guò)程的人常常會(huì)對(duì)這一領(lǐng)域進(jìn)行詳細(xì)研究。
自動(dòng)化單元測(cè)試
有許多種自動(dòng)化所有項(xiàng)目測(cè)試用例的定位和執(zhí)行的方法。一種解決方案是聯(lián)合使用 Ant 的 junit 任務(wù)與嵌入的 fileset 任務(wù)。這樣就可以包括和排除特定目錄中的文件(基于文件名樣式)。另一種選擇是使用 Eclipse 的一個(gè)功能,它可以指定所有測(cè)試所在的和執(zhí)行的目錄。前一種選擇提供了對(duì)運(yùn)行的測(cè)試進(jìn)行過(guò)濾的靈活性(并且由于它是一個(gè)純粹的無(wú)頭(headless)Java 應(yīng)用程序,可以運(yùn)行在幾乎所有地方),后一種選擇可以調(diào)試“動(dòng)態(tài)”包。是否可以結(jié)合這兩種方式的強(qiáng)大和靈活性?
有了 Python 編程語(yǔ)言的 Java 平臺(tái)實(shí)現(xiàn)——Jython,回答是響亮的“可以!”(如果不熟悉 Jython開發(fā),應(yīng)當(dāng)在繼續(xù)本文之前補(bǔ)充這方面知識(shí),更多信息請(qǐng)參閱后面的 參考資料)。利用 Jython 的強(qiáng)大和優(yōu)雅,可以維護(hù)一個(gè)定位文件系統(tǒng)、搜索匹配某種樣式的類和動(dòng)態(tài)編譯 JUnit TestSuite 類的腳本。這個(gè) TestSuite 類像所有其他靜態(tài)定義的類一樣,可以用喜愛的調(diào)試程序容易地調(diào)試。(在本文中使用的例子假定使用的是 Eclipse IDE,不過(guò),我在這里描述的技術(shù)不用做很多修改就可以用于大多數(shù)其他 IDE。)
在進(jìn)行任何設(shè)計(jì)決定時(shí),必須對(duì)所做的選擇和決定的影響進(jìn)行權(quán)衡。在這里,為了得到調(diào)試動(dòng)態(tài)生成的測(cè)試包的能力,必須增加額外的復(fù)雜性。不過(guò),這種復(fù)雜性被 Jython 自身所減輕了:Jython 經(jīng)過(guò)很好測(cè)試并得到很好的支持,并且是開放源代碼的。而且,Python 越來(lái)越成為面向?qū)ο蟮?、平臺(tái)獨(dú)立的編程的事實(shí)上的標(biāo)準(zhǔn)。出于這兩種原因,采用 Jython開發(fā) 的風(fēng)險(xiǎn)很少,特別是它提供了這樣的好處:在創(chuàng)建和調(diào)試動(dòng)態(tài)生成的 JUnit TestSuite 類方面具有無(wú)可匹敵的靈活性。
如果是否采用 Jython 是主要的考慮,那么即使不使用它也可以在解決原來(lái)的問(wèn)題方面有所進(jìn)展。不使用 Jython 的話,可以用一個(gè) Java Property 文件存儲(chǔ)一組類、目錄和包,以在包中加入或者排除測(cè)試。不過(guò),如果選擇使用 Jython,就可以利用整個(gè) Python 語(yǔ)言和運(yùn)行時(shí)來(lái)解決選擇執(zhí)行哪些測(cè)試的問(wèn)題。Python 腳本比 Java Property 文件靈活得多,它只受限于您的想像力。
利用 Jython 與 Java 平臺(tái)的無(wú)縫集成可以創(chuàng)建靜態(tài)定義的、然而是動(dòng)態(tài)構(gòu)建的 TestSuite 類。有大量關(guān)于 JUnit 的教程,不過(guò)還是看下面這兩行代碼作為復(fù)習(xí)。清單 1 是靜態(tài)構(gòu)建 TestSuite 類的一個(gè)例子(這個(gè)例子取自 JUnit: A Cook's Tour,有關(guān)它和其他 JUnit 資源的鏈接請(qǐng)參閱 參考資料):
清單 1.靜態(tài)定義 TestSuite
- public static Test suite() {
- return new TestSuite( MoneyTest.class );
- }
清單 1 表明 TestSuite 是由 Test 類的類實(shí)例組成的。這個(gè)裝具模塊完全利用了這一點(diǎn)。為了分析這個(gè)工具的代碼,應(yīng)從 參考資料中下載本文的示例 JAR 文件。這個(gè)文檔包含兩個(gè)文件:DynamicTestSuite.java 和 getalltests.py,前者是一個(gè)用 Phthon 腳本動(dòng)態(tài)生成 TestSuite 的 JUnit 測(cè)試裝具模塊,后者是一個(gè)搜索匹配特定樣式的文件的 Python 腳本。DynamicTestSuite.java 使用 getalltests.py 構(gòu)建 TestSuite 。可以修改 getalltests.py 以更好地適合自己的項(xiàng)目的需要。
了解測(cè)試裝具模塊
代碼是如何工作的?首先,指派 getalltests.py 獲取一組要執(zhí)行的 Test 類。然后,使用 Jython API 將這個(gè)列表從 Python 運(yùn)行時(shí)環(huán)境中提取出來(lái)。然后使用 Java Reflection API 構(gòu)建在表示 Test 類名的列表中的 String 對(duì)象的類實(shí)例。最后,用 JUnit API 將 Test 添加到 TestSuite 中。這四個(gè)庫(kù)的相互配合可以實(shí)現(xiàn)您的目標(biāo):動(dòng)態(tài)構(gòu)建的 TestSuite 可以像靜態(tài)定義的那樣運(yùn)行。
看一下清單 2 中的 JUnit suite 清單。它是一個(gè)公開 public static TestSuite suite() 方法簽名的 TestCase 。由 JUnit 框架調(diào)用的 suite() 方法調(diào)用 getTestSuite() , getTestSuite() 又調(diào)用 getClassNamesViaJython() 以獲取一組 String 對(duì)象,其中每一個(gè)對(duì)象表示一個(gè)作為包的一部分的 TestCase 類。
清單 2. 動(dòng)態(tài)定義 TestSuite
- /**
- * @return TestSuite A test suite containing all our tests (as found by Python script)
- */
- private TestSuite getTestSuite() {
- TestSuite suite = new TestSuite();
- // get Iterator to class names we're going to add to our Suite
- Iterator testClassNames = getClassNamesViaJython().iterator();
- while( testClassNames.hasNext() ) {
- String classname = testClassNames.next().toString();
- try {
- // construct a Class object given the test case class name
- Class testClass = Class.forName( classname );
- // add to our suite
- suite.addTestSuite( testClass );
- System.out.println( "Added: " + classname );
- }
- catch( ClassNotFoundException e ) {
- StringBuffer warning = new StringBuffer();
- warning.append( "Warning: Class '" ).append( classname ).append( "' not found." );
- System.out.println( warning.toString() );
- }
- }
- return suite;
- }
在開始時(shí),要保證設(shè)置了正確的系統(tǒng)屬性。在內(nèi)部,Jython 將使用 python.home 屬性來(lái)定位它所需要的文件。最終會(huì)調(diào)用 getClassNamesViaJython() 方法,在這里面會(huì)有一些奇妙的事情發(fā)生,如在清單 3 中將會(huì)看到的。
清單 3. 從 Python 運(yùn)行時(shí)提取 Java 對(duì)象
- /**
- * Get list of tests we're going to add to our suite
- * @return List A List of String objects, each representing class name of a TestCase
- */
- private List getClassNamesViaJython() {
- // run python script
- interpreter.execfile( getPathToScript() );
- // extract out Python object named PYTHON_OBJECT_NAME
- PyObject allTestsAsPythonObject = interpreter.get( PYTHON_OBJECT_NAME );
- // convert the Python object to a String[]
- String[] allTests = (String[]) allTestsAsPythonObject.__tojava__( String[].class );
- // add all elements of array to a List
- List testList = new ArrayList();
- testList.addAll( Arrays.asList( allTests ) );
- return testList;
- }
首先,對(duì) Python 文件進(jìn)行判斷。然后,從 Python 運(yùn)行時(shí)提取出一個(gè) PyObject 。這就是得到的對(duì)象,它包含將構(gòu)成測(cè)試包的所有測(cè)試用例的類名(記住 -- PyObject 是 Python 對(duì)象的 Java 運(yùn)行時(shí)對(duì)應(yīng)物)。然后創(chuàng)建具體的 List 并用 PyObject 填充它,使用 __tojava__ 指示 PyObject 將其內(nèi)容轉(zhuǎn)換為一個(gè) Java String 數(shù)組。最后,將控制返回 getTestSuite() ,在這里裝載 Jython 標(biāo)識(shí)的測(cè)試用例,并將它們添加到組合包(composite)中。
在Jython開發(fā)環(huán)境中安裝測(cè)試裝具模塊
現(xiàn)在對(duì)于測(cè)試裝具模塊如何工作已經(jīng)有了很好的認(rèn)識(shí),可能迫不及待要自己試試它了。您將需要完成以下步驟以配置 Eclipse 來(lái)運(yùn)行這個(gè)裝具模塊。(如果使用不同的 IDE,應(yīng)當(dāng)可以容易地針對(duì)您的環(huán)境修改這些步驟。)
安裝 Jython 2.1,如果還沒安裝的話。(鏈接請(qǐng)見 參考資料)。
拷貝 getalltests.py 到主目錄。
編輯 getalltests.py 第 25 行以指定到源文件的根路徑,會(huì)搜索在這個(gè)位置下的所有目錄中與 org 包中 *Text.java 匹配的文件名。
如果有必要,修改第 54 行以改變根包名(例如,改為 com)。
將 DynamicTestSuite.java 拷貝到源樹中。
將以下 JAR 添加到 Eclipse 項(xiàng)目中:
junit.jar (JUnit 框架二進(jìn)制文件,下載信息請(qǐng)參閱 JUnit 的 Web 網(wǎng)站)。
jython.jar(Jython 二進(jìn)制文件,位于 Jython 安裝目錄)。
將 DynamicTestSuite 類裝載到 Eclipse Java 源文件編輯器中。執(zhí)行以下步驟之一:
在 Package Explorer 視圖中選擇 DynamicTestSuite ,或者
按 Ctrl+Shift+T并在 Choose Type 輸入字段鍵入 DynamicTestSuite 。
從文件菜單欄選擇 Run,然后選擇 Debug...。
選擇 JUnit配置。
單擊 New按鈕。將會(huì)創(chuàng)建一個(gè)新的 JUnit 目標(biāo), DynamicTestSuite 應(yīng)當(dāng)預(yù)填入 Test Class 字段。
選擇 Arguments選項(xiàng)卡。
在 VM 參數(shù)文本框中鍵入 -Dpython.home=<path where you installed Jython> 。
單擊 Debug按鈕。
變!現(xiàn)在就有了一個(gè)具體的 JUnit TestCase 類,可以像靜態(tài)定義的包那樣處理它。設(shè)置邊界并進(jìn)行調(diào)試!不需要修改 Test 類,裝具模塊將構(gòu)建一個(gè)包,就像您顯式將每一個(gè) Class 對(duì)象編寫到包中一樣。如要執(zhí)行測(cè)試,可以通過(guò)喜愛的調(diào)試器、編譯工具(如 Ant 或 CruiseControl),或者一個(gè) JUnit 內(nèi)含的 test runner 調(diào)用這個(gè)裝具模塊。
擴(kuò)展這個(gè)裝具模塊
我相信您注意到了除非在運(yùn)行前修改源代碼,否則這個(gè)裝具模塊只能用于一個(gè)項(xiàng)目??梢匀菀椎?cái)U(kuò)展這個(gè)裝具模塊讓它支持多個(gè)項(xiàng)目。一種簡(jiǎn)單的方式是修改 getPathToScript() 以使用指定特定于項(xiàng)目的屬性的系統(tǒng)屬性。
【編輯推薦】