儘管 Ant 自帶的 JUnit task 命令可以非常方便的進行測試用例的選擇,但是有些情況下依然無法滿足特定工程的需要。由於 Ant 自身的良好的擴展性,開發者可以擴展 Ant JUnit,使它能夠通過設置正則表達式來支持更靈活的選擇。在了解了 Ant 的擴展機制之後,擴展的過程其實比較輕鬆。更好的是,擴展之後的 Ant JUnit 命令能夠保持對原有命令的完全兼容性。
了解 Ant JUnit
Apache Ant 是一個基於 Java 的 build 工具,它使用 XML 來配置命令 (Task) 。 Ant 提供了非常豐富的預定義命令,所以在大多數的情況下,開發者只需要使用 Ant 自帶的命令就能完成絕大多數的功能。但如果在某些特定的情況下,為了讓 Ant 能夠實現一些額外的功能,開發者可以擴展預定義的命令,或者自行開發新的命令。而 Ant 的設計者也充分考慮了這一點,易擴展的 Ant 體系使得這個工作也變得非常輕鬆。
和 Ant 一樣,JUnit 也是一個非常流行的工具,在單元測試領域幾乎是事實上的工業標準。幾乎所有的開發工具都對 JUnit 有很好的支持,當然 Ant 也不例外。 Ant 為 JUnit 提供了一個命令 (JUnit Task),能夠讓開發者輕鬆的指定被測試的類、輸出格式和運行方式等。清單 1 是一個典型的 Ant JUnit 配置。
<target name="test" depends="compile" description="Execute the unit test"> <mkdir dir="${report.dir}"/> <property name="cases" value="*Test"/> <junit printsummary="yes" fork="yes" jvm="${fvt.java.home}/bin/java"> <classpath refid="classpath"/> <formatter type="plain" usefile="false"/> <formatter type="xml"/> <!--<test name="${example.ut.test}" todir="${report.dir}"/>--> <batchtest todir="${report.dir}"> <fileset dir="${src.dir}"> <patternset> <include name="**/feature1*.java" /> <include name="**/testcase?.java" /> </patternset> </fileset> </batchtest> </junit> <target> |
在清單 1 的這個 Ant XML 中,請注意到 <batchtest> 中可以包含 <fileset> 等表示的文件集合,可以把 <batchtest> 理解成為一個執行測試用例的容器,只要把測試用例的集合送給它,它就能夠不折不扣的去執行所有的測試用例。另外還可以看到 Ant 除了支持常用的通配符 (wildcard)* 和 ? 之外,還支持一個任意匹配的符號 ** 。通過這幾個通配符的組合使用,Ant JUnit 能夠滿足大多數單元測試的需要。但是在某些特定的實際項目中,默認的 Ant JUnit 命令可能無法滿足要求。讓我們看看下面的工程。
實際項目的需求
在圖 1 的這個工程中,有兩個不同的產品 product 和 product2 。兩個產品各自有不同的 feature,在每個 feature 中還包括不同級別 (level) 的單元測試 TestCaseLX 。在實際的測試中,我們很有可能靈活的選擇要測試的 feature 和級別。比如星期一,我們要選擇 feature1 和 feature3,需要運行所有的級別,但是星期二我們就要運行所有的 L1 的測試用例。
這個工程的特定需求要求我們在選擇測試用例的時候同時考慮測試用例的 level,所處的 product 和 feature 。如果使用 Ant JUnit 默認的命令,配置出各種各樣測試用例組合併不容易。所以可以考慮對 Ant JUnit 命令進行擴展。
在動手做具體的工作之前,讓我們先看看如何去擴展一個 Ant 命令。所有細節都在 Ant 的在線文檔中都有非常清楚地敘述,在這裡筆者只是提取出一些擴展需要知道的關鍵性概念。
擴展 Ant JUnit
現在我們開始擴展 Ant JUnit 。這個過程分成如下的四個步驟。
明確目標
為了滿足項目的需要,最簡單的方式是讓 Ant JUnit 能夠支持正則表達式。我們可以通過送入不同的正則表達式來滿足不同的匹配需求。而正則表達式的編寫本身是一個不難的工作。
為了實現這個目標,一個很好的實踐是先定義出目標 Ant XML,之後根據這個 XML 所需要的屬性來擴展 Ant JUnit 。在這個例子中,假定將會選擇 product 的 feature1 和 feature2 的所有 L1 和 L2 的測試用例進行測試。可以看到,我們為 batchtest 增加了一個子元素 freeselector,它的 include 屬性是一個非常簡單的正則表達式。如果要選擇其他的測試用例進行測試,只需要修改 freeselector 的 include 屬性即可。如清單 2 所示。
<target name="test" depends="compile" description="Execute the unit test"> <mkdir dir="${report.dir}"/> <property name="cases" value="*Test"/> <junit printsummary="yes" fork="yes" jvm="${fvt.java.home}/bin/java"> <classpath refid="classpath"/> <formatter type="plain" usefile="false"/> <formatter type="xml"/> <batchtest todir="${report.dir}"> <freeselector dir="${src.dir}" include=".*/product/(feature1|feature2)/.*L(1|2).java" /> </batchtest> </junit> </target> |
準備源代碼
本次擴展是增強 Ant JUnit 命令的功能,而不是重新製作一個 Ant 命令。首先需要從 Ant 的站點上下載 Ant 的源代碼(http://apache.mirror.phpchina.com/ant/source/apache-ant-1.7.1-src.zip),把其中 JUnit 相關的部分摘取出來,在 Eclipse IDE 中建立一個 Java 工程來對它進行編輯。注意,本文的例子是在 Ant 1.7.1 的基礎上完成,如果讀者使用 Ant 其他版本,請根據實際情況來進行調整。另外,本文使用並修改了 Ant 的源代碼,請務必根據項目的實際情況考慮相應的版權和許可問題。
Ant 的所有源代碼都放在 %src_build_root%\src\main 目錄下,而 JUnit 命令則是放在 org.apache.tools.ant.taskdefs.optional.junit 這個 package 下。將 Junit Package 下的源代碼拷貝到你的 Java 工程下,並且為它提供相應的 JUnit/Ant 庫支持。這樣,你就已經為所有的擴展工作做好準備了。
擴展
如果您已經了解了 Ant 的擴展的基本原理,那麼擴展 Ant JUnit 的過程也就顯得不是很複雜了。 <freeselector> 元素是 <batchtest> 的子元素,根據 <batchtest> 的執行容器的特點,<freeselector> 本質上要做的事情就是提供一個資源列表的選擇,剩下的其他工作 <batchtest> 都會幫你完成。
為此我們創建一個類叫做 FreeSelector,該類實現一個叫做 ResourceCollection 的介面。在 Ant 中,ResourceCollection 代表了一個被選擇的資源集合,而 FreeSelector 正是要為 <batchtest> 提供一個被執行的單元測試集合。之後為該類創建出兩個屬性 include 和 dir,並提供它們的 getter 和 setter 方法。如清單 3 所示。
public class FreeSelector implements ResourceCollection { private String dir=null; private String include=null; public String getDir() { return dir; } public void setDir(String dir) { this.dir = dir; } public String getInclude() { return include; } public void setInclude(String include) { this.include = include; } } |
在完成 FreeSelector 類的框架之後,要為它實現 ResourceCollection 所定義的所有方法,如清單 4 所示。
List list = null; public Iterator iterator() { if (list == null)list = getList(); return list.iterator(); } public int size() { if (list == null)list = getList(); return list.size(); } |
在清單 4 中,我們把 iterator 和 size 兩個方法的具體實現都代理給了另外一個方法 getList 。該列表返回 FreeSelector 對應的資源列表。該方法在清單 5 中得到了實現。實現的基本方法是在 dir 目錄下進行枚舉,並把所有搜索到的文件資源和 include 屬性對應的正則表達式進行匹配,匹配成功的文件資源就被增加到集合中。
private List getList() { String pattern = include; Pattern p = Pattern.compile(pattern); //compile the "include" pattern File baseDir = project.getBaseDir(); File srcFile = new File(baseDir, dir); //get the base dir for all testcases Set l = new HashSet(); Traveller t = new Traveller(srcFile, l, p); t.travel(); //deep-travel the base dir to get all matched resouces List fl=new ArrayList(); Iterator itr=l.iterator(); while(itr.hasNext()){ //wrap the matched list to be Ant resource Object obj=itr.next(); File f=(File)obj; if(f!=null){ FileResource fr=new FileResource(f); fr.setBaseDir(srcFile); fl.add(fr); } } return fl; } |
清單 5 中,Traveller 是一個廣度優先的文件樹的遍歷器(具體代碼可以參考附件中的源代碼),對於每個被遍歷的文件資源,清單 6 的方法被用來檢測它是否符合 include 正則表達式的要求。
void process(File f) { String path = f.getAbsolutePath(); path = path.replaceAll("\\\\", "\\/"); if (p.matcher(path).matches()) { list.add(f); } } |
最後,別忘了在 BatchTest 類中增加一個方法,用來關聯 <batchtest> 和 <freeselector> 。如清單 7 所示。
public void addFreeselector(FreeSelector fs){ add(fs); if(fs.getProject()==null) fs.setProject(project); } |
部署
以上整個源代碼工作只涉及到一個新的類 (FreeSelector) 和一個現有的類 (BatchTest) 。之後就可以開始部署的工作。將修改之後的類文件編譯好,並且打包到 Ant-Junit.jar 中去替代原有的 class 就可以了。
為了使用這個新的 Ant-Junit.jar,可以拷貝新的 Ant-Junit.jar 到 Ant\lib 目錄中,也可以在運行 Ant 的時候,用 -lib 參數來指定你的新 Ant-Junit.jar 。這樣,清單 2 所示的 Ant XML 文件就可以正確地運行了。
小結
Ant 是一個非常易於擴展的體系,為 Ant 現有的命令增加新的特性,或者增加新的命令都是非常方便的。本文展示了如何增加 Ant JUnit 對於正則表達式的支持,從而簡潔而靈活地解決了項目中的實際問題,讀者可以靈活把它應用到具體的項目工作當中。(責任編輯:A6)
[火星人 ] 擴展 Ant JUnit Task已經有362次圍觀