一、
引子junit
原始碼是我仔細閱讀過的第乙個開源專案原始碼。閱讀高手寫的**能學到一些好的程式設計風格和實現思路,這是提高自己程式設計水平行之有效的方法,因此早就想看看這些赫赫有名的框架是怎麼回事了。今天就拿最簡單的
junit
下手,也算開始自己的原始碼分析之路。
junit
作為最著名的單元測試框架,由兩位業界有名人士協力完成,已經經歷了多次版本公升級(了解
junit基礎
、junit實踐
)。junit
總體來說短小而精悍,有不少值得我們借鑑的經驗在裡面;但是也有一些不足存在,當然這對於任何程式來說都是難免的。
下面我們將從整體(巨集觀)和細節(微觀)兩方面來分析
junit
原始碼,以下分析基於
3.8.1
版。二、
巨集觀——
架構與模式
開啟原始碼檔案,你會發現
junit
原始碼被分配到
6個包中:
junit.awtui
、junit.swingui
、junit.textui
、junit.extensions
、junit.framework
、junit.runner
。其中前三個包中包含了
junit
執行時的入口程式以及執行結果顯示介面,它們對於
junit
使用者來說基本是透明的。
junit.runner
包中包含了支援單元測試執行的一些基礎類以及自己的類載入器,它對於
junit
使用者來說是完全透明的。
剩下的兩個包是和使用
junit
進行單元測試緊密聯絡在一起的。其中
junit.framework
包含有編寫一般
junit
單元測試類必須是用到的
junit
類;而junit.extensions
則是對framework
包在功能上的一些必要擴充套件以及為更多的功能擴充套件留下的介面。
junit
提倡單元測試的簡單化和自動化。這就要求
junit
的使用要簡單化,而且要很容易的實現自動化測試。整個
junit
的設計大概也是遵循這個前提吧。整個框架的骨幹僅有三個類組成(下圖所示)。
如果你掌握了
testcase
、testsuite
、basetestrunner
的工作方式,那麼你就可以隨心所欲的編寫測試**了。
下面我們來看看
junit.framework
中類之間的關係,下圖是我根據源**分析出來的,大部分關係都表示了出來。
先來看看各個類的職責。
assert
類提供了
junit
使用的一整套的斷言,這套斷言都被
testcase
繼承下來,
assert
也就變成了透明的。
test
介面是為了統一
testcase
和testsuite
的型別;而
testcase
裡面提供了執行單元測試類的方法;在
testsuite
中則提供了載入單元測試類,檢驗測試類格式等等的方法。
testresult
故名思意就是提供存放測試結果的地方,但是在
junit
中它還帶有一點控制器的功能。testlistener
介面抽象了所有測試監聽者的行為,他包括兩個新增錯誤和失敗的方法,開始測試和結束測試的方法。在
junit
框架中有兩個類實現了這個介面,乙個負責結果列印的
resultprinter
類,乙個是所有
testrunner
的基礎類
basetestrunner
類(這兩個類都不在
framework
包中)。
在這裡指出其中我認為有些不妥的地方。圖上
testcase
和testresult
之間是雙向的依賴關係,而在
uml類圖的關係中指出:依賴關係總是單向的。就讓我們來看看這這個可疑的地方。
testcase
中的**:
/*** runs the test case and collects the results in testresult.
*/public void run(testresult result)
相應得testresult
中的**:
/*** runs a testcase.
*/protected void run(final testcase test)
};runprotected(test, p);//
這個方法就是要執行上面制定的匿名內類
endtest(test);
}testresult
中runprotected
方法:public void runprotected(final test test, protectable p)
catch (assertionfailederror e)
catch (threaddeath e)
catch (throwable e)
}為什麼
junit
裡面會出現這樣奇怪的依賴關係,還有違反單一職責原則的
testresult
?當我看到
junit.extentions
包中的testsetup
時,也許我猜到了作者的用意。我們來看下
testsetup
中有關的**:
public void run(final testresult result)
};//
呼叫了testresult
中的runprotected
方法來執行上面的實現
result.runprotected(this, p);
}這個類的產生是為了彌補
testcase
類的乙個小小的缺陷(具體請見下部分)。注意到在這個類裡面也有和
testresult
類似的匿名內部類。這種匿名內部類全是
protected
介面的無名實現,這裡的目的我認為有兩點:
1)由於內部類可以在接下來的情景中完全不可見,而且不被任何人使用,因此也就隱藏了介面的實現細節。
2)為了提高可重用性,而使用內部模擬較快捷。這樣不管你
protect
方法裡面具體執行什麼,對它錯誤、失敗、異常捕捉的**(
testresult
中的runprotected
方法)就可以重用了。
這也正是為什麼會出現上面那樣奇怪的依賴關係:為了復用,就要讓
runprotected
方法放在乙個
testcase
和testsetup
都能呼叫的地方。
不過我認為為了復用而破壞了系統良好的結構和可讀性,是需要仔細斟酌的。
junit
這樣的設計估計是為了以後框架多次擴充套件後的重用考慮的。
說完了讓我費解的問題。談談我覺得
junit
junit.framework
裡面使用的設計模式。
命令模式:作為輔助單元測試的框架,開發人員在使用它的時候,應該僅僅關心測試用例的編寫,
junit
只是乙個測試用例的執行器和結果檢視器,不應該關心太多關於這個框架的細節。而對於
junit
來說,它並不需要知道請求
testcase
的操作資訊,僅把它當作一種命令來執行,然後把執行測試結果發給開發人員。命令模式正是為了達到這種送耦合的目的。
組合模式
:當系統的測試用例慢慢變得多起來,挨個執行測試用例就成了乙個棘手的問題。作為乙個方便使用的單元測試框架,這一點是必須解決的。因此
junit
裡面提供了
testsuite
的功能,它允許將多個測試用例放到乙個
testsuite
裡面來一次執行;而且要進一步的支援
testsuite
裡面套testsuite
的功能。使用組合模式能夠很好的解決這個問題。
模板模式:
junit
在testcase
這個抽象類中將整個測試的流程設定好了,比如先執行
setup
方法初始化測試前提,在執行測試方法,然後再
teardown
來取消測試設定。而這些步驟的具體實現都延遲到子類中去,也就是你實現的測試類中。
模板模式:
junit
在testcase
這個抽象類中將整個測試的流程設定好了,比如先執行
setup
方法初始化測試前提,在執行測試方法,然後再
teardown
來取消測試設定。而這些步驟的具體實現都延遲到子類中去,也就是你實現的測試類中。
JUnit原始碼分析
junit junit原始碼學習和經驗分享。源 裡沒有密碼,好多開源框架都出自大牛大師之手,是學習的好例子,可惜自己沒啥耐心去看,網上搜尋到牛人解析,記錄下來,學習學習。深入junit原始碼之runner 深入junit原始碼之statement 深入junit原始碼之rule 深入junit原始碼...
junit部分原始碼分析
通過對junit的粗略了解,大致的知道了這裡面的幾種模式 junit原始碼與之spring hibnate原始碼相比,就比較簡單了,但是麻雀雖小,五臟俱全,這裡面用到了幾 種設計模式,也是乙個短小精悍 非常完所的乙個框架。下面講乙個它的整體的框架吧 先得到testresult的物件,然後通過它的物件...
junit部分原始碼分析
通過對junit的粗略了解,大致的知道了這裡面的幾種模式 junit原始碼與之spring hibnate原始碼相比,就比較簡單了,但是麻雀雖小,五臟俱全,這裡面用到了幾 種設計模式,也是乙個短小精悍 非常完所的乙個框架。下面講乙個它的整體的框架吧 先得到testresult的物件,然後通過它的物件...