一、引子
JUnit源碼是我仔細(xì)閱讀過(guò)的第一個(gè)開(kāi)源項(xiàng)目源碼。閱讀高手寫(xiě)的代碼能學(xué)到一些好的編程風(fēng)格和實(shí)現(xiàn)思路,這是提高自己編程水平行之有效的方法,因此早想看看這些赫赫有名的框架是怎么回事了。拿簡(jiǎn)單的JUnit下手,也算開(kāi)始自己的源碼分析之路。
JUnit作為的單元測(cè)試框架,由兩位業(yè)界有名人士協(xié)力完成,已經(jīng)經(jīng)歷了多次版本升級(jí)(了解JUnit基礎(chǔ)、JUnit實(shí)踐)。JUnit總體來(lái)說(shuō)短小而精悍,有不少值得我們借鑒的經(jīng)驗(yàn)在里面;但是也有一些不足存在,當(dāng)然這對(duì)于任何程序來(lái)說(shuō)都是難免的。
下面我們將從整體(宏觀)和細(xì)節(jié)(微觀)兩方面來(lái)分析JUnit源碼,以下分析基于3.8.1版。
二、宏觀——架構(gòu)與模式
打開(kāi)源碼文件,你會(huì)發(fā)現(xiàn)JUnit源碼被分配到6個(gè)包中:junit.awtui、junit.swingui、junit.textui、junit.extensions、junit.framework、junit.runner。其中前三個(gè)包中包含了JUnit運(yùn)行時(shí)的入口程序以及運(yùn)行結(jié)果顯示界面,它們對(duì)于JUnit使用者來(lái)說(shuō)基本是透明的。junit.runner包中包含了支持單元測(cè)試運(yùn)行的一些基礎(chǔ)類(lèi)以及自己的類(lèi)加載器,它對(duì)于JUnit使用者來(lái)說(shuō)是完全透明的。
剩下的兩個(gè)包是和使用JUnit進(jìn)行單元測(cè)試緊密聯(lián)系在一起的。其中junit.framework包含有編寫(xiě)一般JUnit單元測(cè)試類(lèi)必須是用到的JUnit類(lèi);而junit.extensions則是對(duì)framework包在功能上的一些必要擴(kuò)展以及為更多的功能擴(kuò)展留下的接口。
JUnit提倡單元測(cè)試的簡(jiǎn)單化和自動(dòng)化。這要求JUnit的使用要簡(jiǎn)單化,而且要很容易的實(shí)現(xiàn)自動(dòng)化測(cè)試。整個(gè)JUnit的設(shè)計(jì)大概也是遵循這個(gè)前提吧。整個(gè)框架的骨干僅有三個(gè)類(lèi)組成。
如果你掌握了TestCase、TestSuite、BaseTestRunner的工作方式,那么你可以隨心所欲的編寫(xiě)測(cè)試代碼了。
下面我們來(lái)看看junit.framework中類(lèi)之間的關(guān)系,下圖是我根據(jù)源代碼分析出來(lái)的,大部分關(guān)系都表示了出來(lái)。
先來(lái)看看各個(gè)類(lèi)的職責(zé)。Assert類(lèi)提供了JUnit使用的一整套的斷言,這套斷言都被TestCase繼承下來(lái),Assert也變成了透明的。Test接口是為了統(tǒng)一TestCase和TestSuite的類(lèi)型;而TestCase里面提供了運(yùn)行單元測(cè)試類(lèi)的方法;在TestSuite中則提供了加載單元測(cè)試類(lèi),檢驗(yàn)測(cè)試類(lèi)格式等等的方法。TestResult故名思意是提供存放測(cè)試結(jié)果的地方,但是在JUnit中它還帶有一點(diǎn)控制器的功能。
在這里指出其中我認(rèn)為有些不妥的地方。圖上TestCase和TestResult之間是雙向的依賴關(guān)系,而在UML類(lèi)圖的關(guān)系中指出:依賴關(guān)系總是單向的。讓我們來(lái)看看這這個(gè)可疑的地方。
TestCase中的代碼:
/**
* Runs the test case and collects the results in TestResult.
*/
public void run(TestResult result) {
//調(diào)用了result中的run方法,
//TestResult按照名稱來(lái)看應(yīng)該是一個(gè)記錄測(cè)試結(jié)果的類(lèi),怎么還能run?
result.run(this);
}
相應(yīng)得TestResult中的代碼:
/**
* Runs a TestCase.
*/
protected void run(final TestCase test) {
//開(kāi)始測(cè)試
startTest(test);
//這個(gè)匿名內(nèi)類(lèi)的使用一會(huì)再講
Protectable p= new Protectable() {
public void protect() throws Throwable {
//天那,這里又調(diào)用了TestCase里面的runBare方法
test.runBare();
}
};
runProtected(test, p); //這個(gè)方法是要執(zhí)行上面制定的匿名內(nèi)類(lèi)
endTest(test);
}
TestResult中runProtected方法:
public void runProtected(final Test test, Protectable p) {
try {
p.protect();
}
catch (AssertionFailedError e) {
addFailure(test, e); //給TestResult添加失敗記錄
}
catch (ThreadDeath e) { // don't catch ThreadDeath by accident
throw e;
}
catch (Throwable e) {
addError(test, e); //給TestResult添加出錯(cuò)記錄
}
}