理解 JUnit 測(cè)試框架實(shí)現(xiàn)原理和設(shè)計(jì)模式
本文細(xì)致地描述了 JUnit 的代碼實(shí)現(xiàn),在展示代碼流程 UML 圖的基礎(chǔ)上,詳細(xì)分析 JUnit 的內(nèi)部實(shí)現(xiàn)代碼的功能與機(jī)制,并在涉及相關(guān)設(shè)計(jì)模式的地方結(jié)合代碼予以說(shuō)明。另外,分析過(guò)程還涉及 Reflection 等 Java 語(yǔ)言的高級(jí)特征。
概述
在測(cè)試驅(qū)動(dòng)的開(kāi)發(fā)理念深入人心的今天,JUnit 在測(cè)試開(kāi)發(fā)領(lǐng)域的核心地位日漸穩(wěn)定。不僅 Eclipse 將 JUnit 作為默認(rèn)的 IDE 集成組件,而且基于 JUnit 的各種測(cè)試框架也在業(yè)內(nèi)被廣泛應(yīng)用,并獲得了一致好評(píng)。目前介紹 JUnit 書(shū)籍文章雖然較多,但大多數(shù)是針對(duì) JUnit 的具體應(yīng)用實(shí)踐,而對(duì)于 JUnit 本身的機(jī)制原理,只是停留在框架模塊的較淺層次。
本文內(nèi)容完全描述 JUnit 的細(xì)致代碼實(shí)現(xiàn),在展示代碼流程 UML 圖的基礎(chǔ)上,詳細(xì)分析 JUnit 的內(nèi)部實(shí)現(xiàn)代碼的功能與機(jī)制,并在涉及相關(guān)設(shè)計(jì)模式的地方結(jié)合代碼予以說(shuō)明。另外,分析過(guò)程還涉及 Reflection 等 Java 語(yǔ)言的高級(jí)特征。
本文的讀者應(yīng)該對(duì) JUnit 的基本原理及各種設(shè)計(jì)模式有所了解,主要是面向從事 Java 相關(guān)技術(shù)的設(shè)計(jì)、開(kāi)發(fā)與測(cè)試的人員。對(duì)于 C++,C# 程序員也有很好的借鑒作用。
Junit 簡(jiǎn)介
JUnit 的概念及用途
JUnit 是由 Erich Gamma 和 Kent Beck 編寫(xiě)的一個(gè)開(kāi)源的單元測(cè)試框架。它屬于白盒測(cè)試,只要將待測(cè)類繼承 TestCase 類,就可以利用 JUnit 的一系列機(jī)制進(jìn)行便捷的自動(dòng)測(cè)試了。
JUnit 的設(shè)計(jì)精簡(jiǎn),易學(xué)易用,但是功能卻非常強(qiáng)大,這歸因于它內(nèi)部完善的代碼結(jié)構(gòu)。 Erich Gamma 是著名的 GOF 之一,因此 JUnit 中深深滲透了擴(kuò)展性優(yōu)良的設(shè)計(jì)模式思想。 JUnit 提供的 API 既可以讓您寫(xiě)出測(cè)試結(jié)果明確的可重用單元測(cè)試用例,也提供了單元測(cè)試用例成批運(yùn)行的功能。在已經(jīng)實(shí)現(xiàn)的框架中,用戶可以選擇三種方式來(lái)顯示測(cè)試結(jié)果,并且顯示的方式本身也是可擴(kuò)展的。
JUnit 基本原理
一個(gè) JUnit 測(cè)試包含以下元素:
表 1. 測(cè)試用例組成
開(kāi)發(fā)代碼部分 測(cè)試代碼部分 測(cè)試工具部分待測(cè)試類 A |
通過(guò)擴(kuò)展 TestCase 或者構(gòu)造 TestSuit 方法
編寫(xiě)測(cè)試類 B |
一個(gè)測(cè)試運(yùn)行器(TestRunner)R,可以選擇圖形界面或文本界面 |
操作步驟:
將 B 通過(guò)命令行方式或圖形界面選擇方式傳遞給 R,R 自動(dòng)運(yùn)行測(cè)試,并顯示結(jié)果。
JUnit 中的設(shè)計(jì)模式體現(xiàn)
設(shè)計(jì)模式(Design pattern)是一套被反復(fù)使用的、為眾人所知的分類編目的代碼設(shè)計(jì)經(jīng)驗(yàn)總結(jié)。使用設(shè)計(jì)模式是為了可重用和擴(kuò)展代碼,增加代碼的邏輯性和可靠性。設(shè)計(jì)模式的出現(xiàn)使代碼的編制真正工程化,成為軟件工程的基石。
GoF 的《設(shè)計(jì)模式》一書(shū)首次將設(shè)計(jì)模式提升到理論高度,并將之規(guī)范化。該書(shū)提出了 23 種基本設(shè)計(jì)模式,其后,在可復(fù)用面向?qū)ο筌浖陌l(fā)展過(guò)程中,新的設(shè)計(jì)模式亦不斷出現(xiàn)。
軟件框架通常定義了應(yīng)用體系的整體結(jié)構(gòu)類和對(duì)象的關(guān)系等等設(shè)計(jì)參數(shù),以便于具體應(yīng)用實(shí)現(xiàn)者能集中精力于應(yīng)用本身的特定細(xì)節(jié)。因此,設(shè)計(jì)模式有助于對(duì)框架結(jié)構(gòu)的理解,成熟的框架通常使用了多種設(shè)計(jì)模式,JUnit 就是其中的優(yōu)秀代表。設(shè)計(jì)模式是 JUnit 代碼的精髓,沒(méi)有設(shè)計(jì)模式,JUnit 代碼無(wú)法達(dá)到在小代碼量下的高擴(kuò)展性??傮w上看,有三種設(shè)計(jì)模式在 JUnit 設(shè)計(jì)中得到充分體現(xiàn),分別為 Composite 模式、Command 模式以及 Observer 模式。
一個(gè)簡(jiǎn)單的 JUnit 程序?qū)嵗?
我們首先用一個(gè)完整實(shí)例來(lái)說(shuō)明 JUnit 的使用。由于本文的分析對(duì)象是 JUnit 本身的實(shí)現(xiàn)代碼,因此測(cè)試類實(shí)例的簡(jiǎn)化無(wú)妨。本部分引入《 JUnit in Action 》中一個(gè) HelloWorld 級(jí)別的測(cè)試實(shí)例,下文的整個(gè)分析會(huì)以該例子為基點(diǎn),剖析 JUnit 源代碼的內(nèi)部流程。
待測(cè)試類如下:
圖 1. 待測(cè)試代碼
該類只有一個(gè) add 方法,即求兩個(gè)浮點(diǎn)數(shù)之和返回。
下面介紹測(cè)試代碼部分,本文以 JUnit3.8 為實(shí)驗(yàn)對(duì)象,JUnit4.0 架構(gòu)類同。筆者對(duì)原書(shū)中的測(cè)試類做了一些修改,添加了一個(gè)必然失敗的測(cè)試方法 testFail,目的是為了演示測(cè)試失敗時(shí)的 JUnit 代碼流程。
完整的測(cè)試類代碼如下:
圖 2. 測(cè)試類代碼
TestCalculator 擴(kuò)展了 JUnit 的 TestCase 類,其中 testAdd 方法就是對(duì) Calculator.add 方法的測(cè)試,它會(huì)在測(cè)試開(kāi)始后由 JUnit 框架從類中提取出來(lái)運(yùn)行。在 testAdd 中,Calculator 類被實(shí)例化,并輸入測(cè)試參數(shù) 10 和 50,最后用 assertEquals 方法(基類 TestCase 提供)判斷測(cè)試結(jié)果與預(yù)期是否相等。無(wú)論測(cè)試符合預(yù)期或不符合都會(huì)在測(cè)試工具
TestRunner 中體現(xiàn)出來(lái)。
實(shí)例運(yùn)行結(jié)果:
圖 3. 實(shí)例運(yùn)行結(jié)果
從運(yùn)行結(jié)果中可見(jiàn):testAdd 測(cè)試通過(guò)(未顯示),而 testFail 測(cè)試失敗。圖形界面結(jié)果如下:
圖 4. 測(cè)試圖形結(jié)果
JUnit 源代碼分析
JUnit 的完整生命周期分為 3 個(gè)階段:初始化階段、運(yùn)行階段和結(jié)果捕捉階段。
圖 5. JUnit 的完整生命周期圖( 查看大圖 )
初始化階段(創(chuàng)建 Testcase 及 TestSuite)
圖 6. JUnit 的 main 函數(shù)代碼
初始化階段作一些重要的初始化工作,它的入口點(diǎn)在 junit.textui.TestRunner 的 main 方法。該方法首先創(chuàng)建一個(gè) TestRunner 實(shí)例
aTestRunner
。之后 main 函數(shù)中主體工作函數(shù)為?
TestResult r = aTestRunner.start(args) 。
它的函數(shù)構(gòu)造體代碼如下 :
圖 7. junit 的 start(String[]) 函數(shù)
我們可以看出,Junit 首先對(duì)命令行參數(shù)進(jìn)行解析:參數(shù)“ -wait ”(等待模式,測(cè)試完畢用戶手動(dòng)返回)、“ -c ”,“ -v ”(版本顯示)。 -m 參數(shù)用于測(cè)試單個(gè)方法。這是 JUnit 提供給用戶的一個(gè)非常輕便靈巧的測(cè)試功能,但是在一般情況下,用戶會(huì)像本文前述那樣在類名命令行參數(shù),此時(shí)通過(guò)語(yǔ)句:
testCase = args[i];
將測(cè)試類的全限定名將傳給 String 變量 testcase 。
然后通過(guò):
Test suite = getTest(testCase);
將對(duì) testCase 持有的全限定名進(jìn)行解析,并構(gòu)造 TestSuite 。
圖 8. getTest() 方法函數(shù)源代碼
TestSuite 的構(gòu)造分兩種情況 ( 如上圖 ):
- A:用戶在測(cè)試類中通過(guò)聲明 Suite() 方法自定義 TestSuite 。
- B:JUnit 自動(dòng)判斷并提取測(cè)試方法。
JUnit 提供給用戶兩種構(gòu)造測(cè)試集合的方法,用戶既可以自行編碼定義結(jié)構(gòu)化的 TestCase 集合,也可以讓 JUnit 框架自動(dòng)創(chuàng)建測(cè)試集合,這種設(shè)計(jì)融合其它功能,讓測(cè)試的構(gòu)建、運(yùn)行、反饋三個(gè)過(guò)程完全無(wú)縫一體化。
情況 A:
圖 9. 自定義 TestSuite 流程圖
當(dāng) suite 方法在 test case 中定義時(shí),JUnit 創(chuàng)建一個(gè)顯式的 test suite,它
利用 Java 語(yǔ)言的 Reflection 機(jī)制找出名為
SUITE_METHODNAME
的方法,也即 suite 方法:
suiteMethod = testClass.getMethod(SUITE_METHODNAME, new Class[0]);
Reflection 是 Java 的高級(jí)特征之一,借助 Reflection 的 API 能直接在代碼中動(dòng)態(tài)獲取到類的語(yǔ)言編程層面的信息,如類所包含的所有的成員名、成員屬性、方法名以及方法屬性,而且還可以通過(guò)得到的方法對(duì)象,直接調(diào)用該方法。 JUnit 源代碼頻繁使用了 Reflection 機(jī)制,不僅充分發(fā)揮了 Java 語(yǔ)言在系統(tǒng)編程要求下的超凡能力,也使 JUnit 能在用戶自行編寫(xiě)的測(cè)試類中游刃有余地分析并提取各種屬性及代碼,而其它測(cè)試框架需要付出極大的復(fù)雜性才能得到等價(jià)功能。
若 JUnit 無(wú)法找到 siute 方法,則拋出異常,流程進(jìn)入情況 B 代碼;若找到,則對(duì)用戶提供的 suite 方法進(jìn)行外部特征檢驗(yàn),判斷是否為類方法。最后,JUnit 自動(dòng)調(diào)用該方法,構(gòu)造用戶指定的 TestSuite:
test = (Test)suiteMethod.invoke(null, (Object[]) new Class[0]);
情況 B:
圖 10. 自動(dòng)判斷并提取 TestSuite 流程圖
當(dāng) suite 方法未在 test case 中定義時(shí),JUnit 自動(dòng)分析創(chuàng)建一個(gè) test suite 。
代碼由 :
return new TestSuite(testClass);
處進(jìn)入 TestSuite(Class theclass) 方法為 TestSuite 類的構(gòu)造方法,它能自動(dòng)分析 theclass 所描述的類的內(nèi)部有哪些方法需要測(cè)試,并加入到新構(gòu)造的 TestSuite 中。代碼如下:
圖 11. TestSuite 函數(shù)代碼
TestSuite 采用了Composite 設(shè)計(jì)模式。在該模式下,可以將 TestSuite 比作一棵樹(shù),樹(shù)中可以包含子樹(shù)(其它 TestSuite),也可以包含葉子 (TestCase),以此向下遞歸,直到底層全部落實(shí)到葉子為止。 JUnit 采用 Composite 模式維護(hù)測(cè)試集合的內(nèi)部結(jié)構(gòu),使得所有分散的 TestCase 能夠統(tǒng)一集中到一個(gè)或若干個(gè) TestSuite 中,同類的 TestCase 在樹(shù)中占據(jù)同等的位置,便于統(tǒng)一運(yùn)行處理。另外,采用這種結(jié)構(gòu)使測(cè)試集合獲得了無(wú)限的擴(kuò)充性,不需要重新構(gòu)造測(cè)試集合,就能使新的 TestCase 不斷加入到集合中。
在 TestSuite 類的代碼中,可以找到:
private Vector fTests = new Vector(10);
此即為內(nèi)部維護(hù)的“子樹(shù)或樹(shù)葉”的列表。
紅框內(nèi)的代碼完成提取整個(gè)類繼承體系上的測(cè)試方法的提取。循環(huán)語(yǔ)句由 Class 類型的實(shí)例 theClass 開(kāi)始,逐級(jí)向父類的繼承結(jié)構(gòu)追溯,直到頂級(jí) Object 類,并將沿途各級(jí)父類中所有合法的 testXXX() 方法都加入到 TestSuite 中。
合法 testXXX 的判斷工作由:
addTestMethod(methods[i], names, theClass)
完成,實(shí)際上該方法還把判斷成功的方法轉(zhuǎn)化為 TestCase 對(duì)象,并加入到 TestSuite 中。代碼如下圖 :
圖 12. addTestMethod 函數(shù)代碼
首先通過(guò) String name= m.getName(); 利用 Refection API 獲得 Method 對(duì)象 m 的方法名,用于特征判斷。然后通過(guò)方法
isTestMethod(Method m)
中的
return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE);
來(lái)判別方法名是不是以字符串“ test ”開(kāi)始。
而代碼:
if (names.contains(name)) return;
用于在逐級(jí)追溯過(guò)程中,防止不同級(jí)別父類中的 testXXX() 方法重復(fù)加入 TestSuite 。
對(duì)于符合條件的 testXXX() 方法,addTestMethod 方法中用語(yǔ)句:
addTest(createTest(theClass, name));
將 testXXX 方法轉(zhuǎn)化為 TestCase,并加入到 TestSuite 。其中,addTest 方法接受 Test 接口類型的參數(shù),其內(nèi)部有 countTestCases 方法和 run 方法,該接口被 TestSuite 和 TestCase 同時(shí)實(shí)現(xiàn)。這是 Command 設(shè)計(jì)模式精神的體現(xiàn),
Command 模式將調(diào)用操作的對(duì)象與如何實(shí)現(xiàn)該操作的對(duì)象解耦。在運(yùn)行時(shí),TestCase 或 TestSuite 被當(dāng)作 Test 命令對(duì)象,可以像一般對(duì)象那樣進(jìn)行操作和擴(kuò)展,也可以在實(shí)現(xiàn) Composite 模式時(shí)將多個(gè)命令復(fù)合成一個(gè)命令。另外,增加新的命令十分容易,隔離了現(xiàn)有類的影響,今后,也可以與備忘錄模式結(jié)合,實(shí)現(xiàn) undo 等高級(jí)功能。
加入 TestSuite 的 TestCase 由 createTest(theClass, name) 方法創(chuàng)建,代碼如下:
圖 13. CreateTest 函數(shù)代碼( 查看大圖 )
TestSuite 和 TestCase 都有一個(gè)
fName
實(shí)例變量,是在其后的測(cè)試運(yùn)行及結(jié)果返回階段中該 Test 的唯一標(biāo)識(shí),對(duì) TestCase 來(lái)說(shuō),一般也是要測(cè)試的方法名。在?
createTest
?方法中,測(cè)試方法被轉(zhuǎn)化成一個(gè) TestCase 實(shí)例,并通過(guò):
((TestCase) test).setName(name);
用該方法名標(biāo)識(shí) TestCase 。其中,test 對(duì)象也是通過(guò) Refection 機(jī)制,通過(guò) theClass 構(gòu)建的:
test = constructor.newInstance(new Object[0]);
注意:theClass 是圖 8 中 getTest 方法的 suiteClassName 字符串所構(gòu)造的 Class 類實(shí)例,而后者其實(shí)是命令行參數(shù)傳入的帶測(cè)試類 Calculator,它繼承了 TestCase 方法。因此,theClass 完全具備轉(zhuǎn)化的條件。
至此整個(gè)流程的初始化完成。
測(cè)試驅(qū)動(dòng)運(yùn)行階段(運(yùn)行所有 TestXXX 型的測(cè)試方法)
由圖 7 所示 , 我們可以知道初始化完畢,即 testsuit() 創(chuàng)建好后 , 便進(jìn)入方法 :
doRun(suite, wait);
代碼如下 :
圖 14. doRun 函數(shù)代碼
該方法為測(cè)試的驅(qū)動(dòng)運(yùn)行部分,結(jié)構(gòu)如下:
- 創(chuàng)建 TestResult 實(shí)例。
- 將 junit.textui.TestRunner 的監(jiān)聽(tīng)器 fPrinter 加入到 result 的監(jiān)聽(tīng)器列表中。
其中,fPrinter 是 junit.textui.ResultPrinter 類的實(shí)例,該類提供了向控制臺(tái)輸出測(cè)試結(jié)果的一系列功能接口,輸出的格式在類中定義。 ResultPrinter 類實(shí)現(xiàn)了 TestListener 接口,具體實(shí)現(xiàn)了 addError、addFailure、endTest 和 startTest 四個(gè)重要的方法,這種設(shè)計(jì)是 Observer 設(shè)計(jì)模式的體現(xiàn),在 addListener 方法的代碼中:
public synchronized void addListener(TestListener listener) { fListeners.addElement(listener); }
將?
ResultPrinter
?對(duì)象加入到?
TestResult
?對(duì)象的監(jiān)聽(tīng)器列表中,因此實(shí)質(zhì)上 TestResult 對(duì)象可以有多個(gè)監(jiān)聽(tīng)器顯示測(cè)試結(jié)果。第三部分分析中將會(huì)描述對(duì)監(jiān)聽(tīng)器的消息更新。
- 計(jì)時(shí)開(kāi)始。
- run(result) 測(cè)試運(yùn)行。
- 計(jì)時(shí)結(jié)束。
- 統(tǒng)一輸出,包括測(cè)試結(jié)果和所用時(shí)間。
其中最為重要的步驟為 run(result) 方法,代碼如下。
圖 15. run 函數(shù)代碼
Junit 通過(guò)?
for (Enumeration e= tests(); e.hasMoreElements(); ){ …… }
?對(duì) TestSuite 中的整個(gè)“樹(shù)結(jié)構(gòu)”遞歸遍歷運(yùn)行其中的節(jié)點(diǎn)和葉子。此處 JUnit 代碼頗具說(shuō)服力地說(shuō)明了 Composite 模式的效力,run 接口方法的抽象具有重大意義,它實(shí)現(xiàn)了客戶代碼與復(fù)雜對(duì)象容器結(jié)構(gòu)的解耦,讓對(duì)象容器自己來(lái)實(shí)現(xiàn)自身的復(fù)雜結(jié)構(gòu),從而使得客戶代碼就像處理簡(jiǎn)單對(duì)象一樣來(lái)處理復(fù)雜的對(duì)象容器。每次循環(huán)得到的節(jié)點(diǎn) test,都同 result 一起傳遞給 runTest 方法,進(jìn)行下一步更深入的運(yùn)行。
圖 16. junit.framework.TestResult.run 函數(shù)代碼
這里變量 P 指向一個(gè)實(shí)現(xiàn)了 Protectable 接口的匿名類的實(shí)例,Protectable 接口只有一個(gè) protect 待實(shí)現(xiàn)方法。而 junit.framework.TestResult.runProtected(Test, Protectable) 方法的定義為:
public void runProtected(final Test test, Protectable p) { try { p.protect(); } catch (AssertionFailedError e) { addFailure(test, e); } catch (ThreadDeath e) { // don't catch ThreadDeath by accident throw e; } catch (Throwable e) { addError(test, e); } }
可見(jiàn) runProtected 方法實(shí)際上是調(diào)用了剛剛實(shí)現(xiàn)的 protect 方法,也就是調(diào)用了 test.runBare() 方法。另外,這里的 startTest 和 endTest 方法也是 Observer 設(shè)計(jì)模式中的兩個(gè)重要的消息更新方法。
以下分析 junit.framework.TestCase.runBare() 方法:
圖 17. junit.framework.TestCase.runBare() 函數(shù)代碼
在該方法中,最終的測(cè)試會(huì)傳遞給一個(gè) runTest 方法執(zhí)行,注意此處的 runTest 方法是無(wú)參的,注意與之前形似的方法區(qū)別。該方法中也出現(xiàn)了經(jīng)典的 setUp 方法和 tearDown 方法,追溯代碼可知它們的定義為空。用戶可以覆蓋兩者,進(jìn)行一些 fixture 的自定義和搭建。 ( 注意:tearDown 放在了 finally{} 中,在測(cè)試異常拋出后仍會(huì)被執(zhí)行到,因此它是被保證運(yùn)行的。 )
主體工作還是在 junit.framework.TestCase.runTest() 方法中 , 代碼如下 :
圖 18. junit.framework.TestCase.runTest() 函數(shù)代碼
該方法最根本的原理是:利用在圖 13 中設(shè)定的 fName,借助 Reflection 機(jī)制,從 TestCase 中提取測(cè)試方法:
runMethod = getClass().getMethod(fName, (Class[]) null);
為每一個(gè)測(cè)試方法,創(chuàng)建一個(gè)方法對(duì)象 runMethod 并調(diào)用:
runMethod.invoke(this, (Object[]) new Class[0]);
只有在這里,用戶測(cè)試方法的代碼才開(kāi)始被運(yùn)行。
在測(cè)試方法運(yùn)行時(shí),眾多的 Assert 方法會(huì)根據(jù)測(cè)試的實(shí)際情況,拋出失敗異常或者錯(cuò)誤。也是在“ runMethod.invoke(this, (Object[]) new Class[0]); ”這里,這些異常或錯(cuò)誤往上逐層拋出,或者被某一層次處理,或者處理后再次拋出,依次遞推,最終顯示給用戶。
流程圖如下 :
圖 19. JUnit 執(zhí)行測(cè)試方法,并在測(cè)試結(jié)束后將失敗和錯(cuò)誤信息通知所有 test listener
測(cè)試結(jié)果捕捉階段(返回 Fail 或 Error 并顯示)
通過(guò)以下代碼,我們可以看出失敗由第一個(gè) catch 子句捕獲,并交由 addFailure 方法處理,而錯(cuò)誤由第三個(gè) catch 子句捕獲,并交由 addError 方法處理。
圖 20. 失敗處理函數(shù)代碼
圖 21. 失敗處理流程圖
JUnit 執(zhí)行測(cè)試方法,并在測(cè)試結(jié)束后將失敗和錯(cuò)誤信息通知給所有的 test listener 。其中 addFailure、addError、endTest、startTest 是 TestListener 接口的四大方法,而 TestListener 涉及到 Observer 設(shè)計(jì)模式。
我們嘗試看看 addFailure 方法的代碼:
圖 22. addFailure 方法的代碼
此處代碼將產(chǎn)生的失敗對(duì)象加入到了 fFailures,可聯(lián)系 圖 2,此處的結(jié)果在程序退出時(shí)作為測(cè)試總體成功或失敗的判斷依據(jù)。而在 for 循環(huán)中,TestResult 對(duì)象循環(huán)遍歷觀察者(監(jiān)聽(tīng)器)列表,通過(guò)調(diào)用相應(yīng)的更新方法,更新所有的觀察者信息,這部分代碼也是整個(gè) Observer 設(shè)計(jì)模式架構(gòu)的重要部分。
根據(jù)以上描述,JUnit 采用 Observer 設(shè)計(jì)模式使得 TestResult 與眾多測(cè)試結(jié)果監(jiān)聽(tīng)器通過(guò)接口 TestListenner 達(dá)到松耦合,使 JUnit 可以支持不同的使用方式。目標(biāo)對(duì)象(TestResult)不必關(guān)心有多少對(duì)象對(duì)自身注冊(cè),它只是根據(jù)列表通知所有觀察者。因此,TestResult 不用更改自身代碼,而輕易地支持了類似于 ResultPrinter 這種監(jiān)聽(tīng)器的無(wú)限擴(kuò)充。目前,已有文本界面、圖形界面和 Eclipse 集成組件三種監(jiān)聽(tīng)器,用戶完全可以開(kāi)發(fā)符合接口的更強(qiáng)大的監(jiān)聽(tīng)器。
出于安全考慮,cloneListeners() 使用克隆機(jī)制取出監(jiān)聽(tīng)器列表:
private synchronized Vector cloneListeners() { return (Vector)fListeners.clone(); }
TestResult 的 addFailure 進(jìn)一步調(diào)用 ResultPrinter 的 addFailure:
圖 23. ResultPrinter 的 addFailure 函數(shù)代碼
這里并沒(méi)有將錯(cuò)誤信息輸出,而只是輸出了錯(cuò)誤類型:“ F “。錯(cuò)誤信息由圖 14 中的:
fPrinter.print(result, runTime);
統(tǒng)一輸出。
這些設(shè)計(jì)細(xì)節(jié)皆可以由 TestRunner 的實(shí)現(xiàn)者自己掌握。
?
總結(jié)
鑒于 JUnit 目前在測(cè)試領(lǐng)域的顯赫地位,以及 JUnit 實(shí)現(xiàn)代碼本身編寫(xiě)的簡(jiǎn)潔與藝術(shù)性,本文的詳盡分析無(wú)論對(duì)于測(cè)試開(kāi)發(fā)的實(shí)踐運(yùn)用、基于 JUnit 的高層框架的編寫(xiě)、以及設(shè)計(jì)模式與 Java 語(yǔ)言高級(jí)特征的學(xué)習(xí)都具有多面的重要意義。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
