java之單元測試–程式員必備知識

來源:互聯網
上載者:User
文章目錄
  • 使用JUnit架構實現Java單元測試
使用JUnit架構實現Java單元測試

 

隨著軟體項目的逐漸增大,軟體測試在軟體開發中的地位顯得越來越重要。如果軟體項目沒有良好的測試流程,隨著系統的增大,無論專案管理人員還是軟體開發人員都會對項目的前景失去信心,甚至會對項目的目標產生分歧,因為長期以來沒有對程式碼和系統設計進行有效控制,很多問題都被暫時掩蓋或逐漸演化成其他的問題。軟體開發週期越長,就會使得問題進化的版本越多,最後造成的結果是“剪不斷,理還亂”。
  單元測試是整個測試流程中最基礎的部分,它們要求程式員儘可能早地發現問題,並給予控制,這是其一。另外,如果整合測試出現問題,它們可以協助診斷。這樣就為在軟體開發流程中建立高效的事件反應機制打下了堅實基礎。
  JUnit就是為Java程式開發人員實現單元測試提供一種架構,使得Java單元測試更規範有效,並且更有利於測試的整合。
  JUnit的內部結構
  JUnit的軟體結構
  JUnit 共有七個包,核心的包就是junit.framework 和junit.runner。Framework包負責整個測試對象的構架,Runner負責測試驅動。
  JUnit的類結構
  JUnit有四個重要的類:TestSuite、TestCase、TestResult、TestRunner。前三個類屬於Framework包,後一個類在不同的環境下是不同的。這裡使用的是文本測試環境,所以用的是 junit.textui.TestRunner。各個類的職責如下:
  1.TestResult,負責收集TestCase所執行的結果,它將結果分為兩類,客戶可預測的Failure和沒有預測的Error。同時負責將測試結果轉寄到TestListener(該介面由TestRunner繼承)處理;
  2.TestRunner,客戶對象調用的起點,負責對整個測試流程的跟蹤。能夠顯示返回的測試結果,並且報告測試的進度。
  3.TestSuite, 負責封裝和運行所有的TestCase。
  4.TestCase, 客戶測試類別所要繼承的類,負責測試時對客戶類進行初始化,以及測試方法調用。
  另外還有兩個重要的介面:Test和TestListener。
  1.Test, 包含兩個方法:run() 和countTestCases(),它是對測試動作特徵的提取。
  2.TestListener, 包含四個方法:addError()、addFailure()、startTest()和endTest(),它是對測試結果的處理以及測試驅動過程的動作特徵的提取。
  下面給出的兩個類圖(篇幅有限,只顯示主要部分)很好地闡明了類之間的關係,以及junit的設計目標(1)。測試案例的類採用Composite模式。這樣,客戶的測試對象就轉變成一個“部分—整體”的階層。客戶代碼僅需要繼承類TestCase,就可以輕鬆的與已有的其他對象組合使用,從而使得單元測試的整合更加方便。
   
  圖1 測試結構圖
  圖2是測試跟蹤類圖。圖2左邊TestSuite包含了測試對象集合,右邊包含了測試結果集。具體如何處理結果,以及包含哪些測試對象,並沒有立即得出結論,而是盡量地延遲到具體實現的時候。例如,實現介面TestListener的JUnit中就含有:junit.awtui.TestRunner、junit.swingui. TestRunner、junit.ui.TestRunner等,甚至客戶用自己的類實現TestListener,從而達到多樣化的目的。
   
   圖2 測試跟蹤圖
  從以上兩個類圖,可以瞭解JUnit對單元測試的基本思路,這個架構的核心就是結果集和案例集。

  JUnit的實現流程
  典型的使用JUnit的方法就是繼承TestCase類,然後重載它的一些重要方法:setUp()、teardown()、runTest()(這些都是可選的),最後將這些客戶對象組裝到一個TestSuite對象中,交由 junit.textui.TestRunner.run (案例集) 驅動。下面分析案例集是如何運轉的。
  圖3基本上闡述JUnit的測試流程架構。我們將從不同的角度來詳細分析這個圖。
  
  圖3 測試順序圖表
  首先,從對象的建立上來分析。客戶類負責建立Suite和aTestRunner。注意,類TestRunner含有一個靜態函數Run(Test),它自建立本身,然後調用doRun()。客戶類調用的一般是該函數,其代碼如下:
  static public void run(Test suite)
  {
   TestRunner aTestRunner= new TestRunner();//建立測試驅動
   aTestRunner.doRun(suite, false);//用測試驅動運行測試集
  }
  Suite對象負責建立眾多的測試案例,並將它們包容到本身。客戶測試案例繼承TestCase類,它將類,而不是對象傳給Suite對象。Suite對象負責解析這些類、提取建構函式和待測試方法。以待測試方法為單位構造測試案例,測試案例的fName就是待測試方法名。測試結果集由aTestRunner建立。這似乎同先前闡述的類圖有些矛盾,那裡闡述了一個測試集可以包含很多個不同的測試驅動,似乎先建立結果集比較理想。顯然,這裡對測試結果的處理只採用了一種方式,所以這樣做同樣可行。
  其次,從測試動作的執行上來分析,測試真正是從suite.run(result) 開始的。其代碼如下:
  
  public void run(TestResult result)
  {
  //從案例集中獲得所有測試案例,分別執行
   for (Enumeration e= tests(); e.hasMoreElements(); )
   {
   if (result.shouldStop() )
   break;
   Test test= (Test)e.nextElement();
   runTest(test, result);
   }
  }
  一旦測試案例開始執行,首先使用一個回調策略將自身交由Result。這樣做的每一步測試,測試驅動aTest Runner都可以跟蹤處理。這無形中建立了一個龐大的監視系統,隨時都可以對所發生的事件給予不同等級的關注。
  我們分析一下涉及到的動作行為的設計模式:
  1. Template Method (模板方法)類行為模式,它的實質就是首先建立方法的骨架,而儘可能地將方法的具體實現向後推移。TestCase.runBare()就採用了這種模式,客戶類均可以重載它的三個方法,這樣使得測試的延展性得到提高。
  public void runBare() throws Throwable
  {
   setUp();
   try {runTest();}
   finally {tearDown();}
  }
  2. Command (命令)對象行為模式,其實質就是將動作封裝為一個對象,而不關心動作的接收者。這樣動作的接收者可以一直到動作具體執行時才需確定。介面Test就是一個Command集,使得不同類的不同測試方法可以通過同一種介面Test構造其架構結構。這樣對測試的整合帶來了很多方便。
  JUnit的Exception的拋出機制
  JUnit的異常層次分為三層:1.Failure,客戶預知的測試失敗,可以被Assert方法檢測到;2. Error,客戶測試的意外造成的;3.Systemerror, JUnit的線程死亡級異常,這種情況一般很少發生。JUnit的這三種異常在TestResult類的RunProtected()方法得到很好體現。這裡用Protectable介面封裝了Test的執行方法,其實p.protect執行的就是test.runBare()。
  
  public void runProtected(final Test test, Protectable p)
  {
   try {p.protect();}
   catch (AssertionFailedError e) 
   {addFailure(test, e);}
   catch (ThreadDeath e)
   {rethrow e;}
   catch (Throwable e)
   {addError(test, e);}
  }
  代碼首先檢查是否是Assertion FailedError,然後判斷是否是嚴重的ThreadDeath。這種異常必須Rethrow,才能保證線程真正的死亡,如果不是,說明它是一種意外。
  前兩種異常均儲存在測試結果集中,等到整個測試完成,依次列印出來供客戶參考。
  實施JUnit的幾點建議
  從以上的分析中,可以瞭解JUnit的結構和流程,但是在實際應用JUnit時,有幾點建議還需要說明,如下:
  1. 客戶類可以重載runTest(),它的預設實現是調用方法名為fName的測試方法。如果客戶不是使用TestSuite載入TestCase,就尤其需要對其重載,當然這種方式並不贊成使用,不利於整合。另外,setUp()和tearDown()的功能似乎與建構函式雷同,但如果測試案例之間具有類繼承關係,採用建構函式初始化一些參數就會造成資料的混亂,不利於判定測試結果的有效性。
  2. 待測試函數的調用順序是不確定的,採用的資料結構是Vector()。如果需要有循序關聯性,可以將它們組合到一起,然後用同一個測試方法。
  3. 為了使測試結果清晰明了,程式中最好不要有列印輸出,要麼程式的列印輸出與JUnit測試的列印輸出不要用同一個資料來源System.out。其實這是兩種測試習慣,直接列印輸出是較傳統的,從測試動機上考慮它也是較隨意的,並且結果需要人工觀察。如果直接列印輸出較多的話,觀察者可能無法獲得滿意的結果。
  此外,如何擴充這個測試架構呢? junit.extensions包給出了幾點提示。我們可以使用junit.extensions. ActiveTest在不同的線程中運行一個測試執行個體。 對於要對測試案例添加新的功能可以採用Decorator模式,可以參考junit.extensions.TestDecorator以及它的子類junit.extensions.TestSetup、junit.extensions.RepeatedTest。這些僅僅提供了一些拓寬的思路,涉及到具體測試目標,還需進一步地挖掘。 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.