junit是我們平時開發中天天用到的測試架構,為了了解器内部隐藏的機關,特意分析了一下源碼,這裡我們用的是Junit3.8版本。
1.包的劃分

junit.awtui,這個是junit的awt實作的ui界面元件
junit.extensions這個是junit核心功能之外的擴充點,對TestCase的裝飾,Demorator模式的很好的例子
junit.framework,這個是junit的核心功能,像我們平時常用的TestCase,TestSuit類都是在這的,還有Assert類,提供了我們測試中常用的斷言靜态方法
junit.runner,這個包下是運作TestCase的核心類,最重要的是TestListener和BaseTestRunner類,這個後面會解釋到
junit.swingui,這個包的作用和junit.awtui是完全一直的,隻不過是用Swing的方式實作而已
junit.textui,功能等價與junit.awtui和junit.swingui,junit控制台輸出的一個實作,下面的源碼解讀中,我們以textui包作為ui輸出解釋
從上面的分析可以看出,Junit運作的最小單元是framework+runner+ui-簡潔就是美
2.架構的核心類
junit的源碼非常小,但是設計的非常巧妙,核心類/接口大約有5-6個的樣子,通過分析,我們可以得出一下的核心類圖
Test接口:這個是最頂層的測試實體抽象,接口中隻定義了兩個方法,countTestCases()表示目前測試實體有多少個測試用例,另一個就是核心的run方法了
TestCase類--Test接口的一個實作類,也是我們平時常用的測試父類,表示一個要進行測試的對象,在實際代碼中,代表我們一個個的測試方法(junit3.8中隻那些繼承了TestCase類中那些以test開頭的測試方法)
TestSuit類--Test接口的另一個實作,見名知其意,是一組測試用例類,和TestCase是一對多關系
TestResult類-這個是測試結果的包裹類,提供了常用的方法,如測試失敗時,測試成功時做一些處理,基于Observer模式實作
TestListener接口--整個junit核心基于ObserVer模式實作,這個接口則是對Observer的抽象
BaseTestRunner類--TestListener的抽象實作,定義了通用的公共方法,而TestRunner(swing,atw,textui)則是擴充了基類的功能,提供了ui的展示層功能
2.源碼解讀
上面介紹了架構的核心類,接下來看下我們的核心類之間如何協調來完成測試的
3.junit的設計思想
3.1觀察者模式
觀察者模式的核心對象有兩個,一個是要監控的對象,junit裡就是一個個測試用例TestCase,另一個是觀察者,當監控對象發生變化時(測試用例開始前,出錯,運作失敗,運作結束事件),做出一些操作(GUI更新)。從第一節的類層次中可以看出,junit本身對TestListener做了封裝,上層可以通過繼承這個類來快速的開發出另一種觀察者的實作,如我們在Eclipse中運用junit插件,其實就是插件中擴充一下BaseTestRunner的功能,增加eclipse展示的ui而已,而底層的核心邏輯是不需要變化的
3.2模闆方法模式
可以這樣了解模闆方法模式,在父類中定義一個算法的骨架,而具體的實作類則放在子類中去實作,junit很多地方都充斥着這種思想
TestTest.run方法就是一個模闆方法startTest,runTest,endTest,依次執行,我們熟知的TestCase中,每個測試方法執行前都會運作setUp,測試方法運作完後運作tearDown方法,這個也是個模闆方法模式的實作,在TestCase中有個我們不直接調的核心模闆方法
/**
* Runs a TestCase.
*/
protected void run(final TestCase test) {
startTest(test);
Protectable p= new Protectable() {
public void protect() throws Throwable {
test.runBare();
}
};
runProtected(test, p);
endTest(test);
}
這下子搞明白了,這個方法則是在更頂層的TestResult.run中調用runBare(),然後runBare()中調用我們熟知的那些setUp,tearDown
3.3約定大于配置
測試junit3測試用例的同學都知道,要寫一個讓junit可以運作的測試用例,我們的測試方法必須是public void test***()這種格式的,否則junit不會執行,為什麼呢?看下junit的實作細節
private void addTestMethod(Method m, Vector names, Class theClass) {
String name= m.getName();
if (names.contains(name))
return;
if (! isPublicTestMethod(m)) {
if (isTestMethod(m))
addTest(warning("Test method isn't public: "+m.getName()));
names.addElement(name);
addTest(createTest(theClass, name));
private boolean isTestMethod(Method m) {
Class[] parameters= m.getParameterTypes();
Class returnType= m.getReturnType();
return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE);//這個地方判斷方法以test開頭是寫死的:)
這種做法有兩種争論,一種是支援派,有這樣一個約定,大家都沒的選,樣式統一規範,看一眼就知道是個測試方法,這種方式和maven項目的預設項目結構有點類似--要到這個山頭混,就得聽山大王的:)
另一種是反對派,認為這種方式太過死闆,為什麼測試方法不可以有參數,為什麼不能不以test開頭,這可能也是後來testgn,junit4橫空出世的原因吧:)
4.收獲&總結
一個優秀的架構不在于它有多大,内部用了多先進的技術,用最簡單的東西實作最常用的功能就可以了,研究源碼主要收獲如下
1).系統設計(特别是架構設計)一定要簡潔易懂,簡潔就是美
2).為系統設計留有餘地--系統的核心在于定義核心邏輯和算法,但是具體實作的多樣化可以留給後來人自由擴充--對接口和抽象程式設計,定義鈎子方法..