三、微觀——執行流程與代碼風格
來過一遍JUnit的執行流程吧,這樣你就能對JUnit有個清晰的認識,雖然作為一個使用者這完全是不必要的。從《JUnit in Action》直接拿來一張JUnit流程圖。
哦,也許你看暈了,我來當下導游好了。上面已經提到了TestRunner是BaseTestRunner的子類,在三個不同的ui包中各有一個TestRunner。這里我們僅以junit.textui包中的為例。
TestRunner作為入口程序是怎么被啟動的呢?習慣了使用容器的我們現在也許很少考慮這個問題。那我們在TestRunner類里面找找吧,你看,你發現了這個:
public static void main(String args[]) {
這不是我們寫小桌面程序時經常打交道的main方法么?對,就這么簡單。
從這個main方法入口,首先JUnit將要分析命令行的參數,然后將檢查測試類是否包含符合標準的suite方法,如果有就將執行方法中的內容(見圖中0、1部分);如果沒有找到將自動生成一個TestSuite,這樣就跳過了圖中的0部分,并將測試用例類作為參數傳入(見圖中1部分)。
上面得到了一個TestSuite類型的對象,現在就可以運行測試了,不過在運行前要先加載TestResult和TestListener的對象(見圖中2部分),用來監聽和記錄測試結果信息。剩下的在圖中可以很容易的看懂了,你可以參照源碼瀏覽一遍。
這里提出我的一點疑問。注意到JUnit實踐中提示將測試類中每個測試方法公用的初始化步驟放到setup方法中。這似乎會給你一種錯覺,那就是你會認為setup與tearDown中的語句對于一個測試類中的所有測試方法只會運行一次。但是實際上JUnit在實現上卻出乎意料,setup對于測試類中的每個測試方法都回運行一遍。意思就是說,你把公用的初始化代碼放到setup方法中僅僅是在代碼結構上實現了重用,而沒有起到任何優化系統的作用。比如你在setUp中初始化數據庫連接,那么這個過程將被執行不只一次,這可有點……。我們來看下代碼:
//theClass為得到的TestCase類,name為此類其中的一個方法
static public Test createTest(Class theClass, String name) {
Constructor constructor;
try {
constructor= getTestConstructor(theClass);
……
Object test;
try {
//以下內容為獲得一個TestCase對象,并將方法名稱傳入這個對象
if (constructor.getParameterTypes().length == 0) {
test= constructor.newInstance(new Object[0]);
if (test instanceof TestCase)
((TestCase) test).setName(name);
} else {
test= constructor.newInstance(new Object[]{name});
}
……
//返回這個對象
return (Test) test;
}
再看下運行處的代碼,下面的方法是運行在setUp和tearDown中間的
protected void runTest() throws Throwable {
//fName就是testcase對象所擁有的那個方法的名稱
assertNotNull(fName);
Method runMethod= null;
try {
//根據方法名由反射得到方法
runMethod= getClass().getMethod(fName, null);
}
……
//執行測試方法
runMethod.invoke(this, new Class[0]);
……
}
這樣每執行一個測試方法就要運行一遍setUp和tearDown方法,大概就是這樣一個過程:
恩……,也許這是為了兼容老的版本,也許是……。還好,JUnit提供了一個補救的擴展類,那就是我們上面提到的TestSetup,在這里類里面真正的實現了setUp、tearDown方法的提取使用。你在使用的時候,通過繼承來實現自己的setUp、tearDown方法,并使用裝飾模式獨有的調用方式來使用它就可以了。
在閱讀的過程中,代碼風格上給我最明顯的感覺就是,代碼基本上全多做到了細化,將每個功能點單獨提取到一個方法中,這樣提高了代碼的可復用性?墒窃陂喿x的時候,在方法間頻繁的跳躍,實在不是件好事,如果沒有IDE的幫助,我非要暈掉不可。
因此我認為在提高代碼重用上還是要堅持這樣的一條原則:到必要的時候再下手。就是說,在你剛開始寫代碼的時候不要考慮什么重用和擴展,只有當你真正需要復用某段代碼或者擴展系統時,在動手吧(記得在某位牛人的書上是這么來比喻的:讓第一顆子彈打中你)。
JUnit中使用的是老版本java collection,這大概是因為JUnit最初版本出現的時候還沒有新版collection推出。這種代碼不應該出現在我們現在編寫的代碼中了,請注意。
四、總結
四、總結
四、總結
好了,基本上分析完了JUnit的代碼,不知道你學到了什么。希望本文能夠起到拋磚引玉的作用。
延伸閱讀
文章來源于領測軟件測試網 http://www.kjueaiud.com/