單元測試代碼編寫

      單元測試是在軟件開發過程中要進行的最低級別的測試活動,在單元測試活動中,軟件的獨立單元將在與程序的其他部分相隔離的情況下進行測試。 單元測試不僅僅是作爲無錯編碼一種輔助手段在一次性的開發過程中使用,單元測試必須是可重複的,無論是在軟件修改,或是移植到新的運行環境的過程中。因此,所有的測試都必須在整個軟件系統的生命週期中進行維護。

      多數講述單元測試的文章都是以Java爲例,本文以C++爲例,後半部分所介紹的單元測試工具也只介紹C++單元測試工具。下面的示例代碼的開發環境是VC6.0。

產品類

  class CMyClass

  {

  public:

  int Add(int i, int j);

  CMyClass();

  virtual ~CMyClass();

  private:

  int mAge; //年齡

  CString mPhase; //年齡階段,如"少年","青年"

  };

  建立對應的測試類CMyClassTester,爲了節約編幅,只列出源文件的代碼:

  void CMyClassTester::CaseBegin()

  {

  //pObj是CMyClassTester類的成員變量,是被測試類的對象的指針,

  //爲求簡單,所有的測試類都可以用pObj命名被測試對象的指針。

  pObj = new CMyClass();

  }

  void CMyClassTester::CaseEnd()

  {

  delete pObj;

  }

  測試類的函數CaseBegin()和CaseEnd()建立和銷燬被測試對象,每個測試用例的開頭都要調用CaseBegin(),結尾都要調用CaseEnd()。

產品函數

  接下來,我們建立示例的產品函數:

  int CMyClass::Add(int i, int j)

  {

  return i+j;

  }

  和對應的測試函數:

  void CMyClassTester::Add_int_int()

  {

  }

  把參數表作爲函數名的一部分,這樣當出現重載的被測試函數時,測試函數不會產生命名衝突。下面添加測試用例:

  void CMyClassTester::Add_int_int()

  {

  //第一個測試用例

  CaseBegin();{ //1

  int i = 0; //2

  int j = 0; //3

  int ret = pObj->Add(i, j); //4

  ASSERT(ret == 0); //5

  }CaseEnd(); //6

  }

解釋

  第1和第6行建立和銷燬被測試對象,所加的{}是爲了讓每個測試用例的代碼有一個獨立的域,以便多個測試用例使用相同的變量名。

  第2和第3行是定義輸入數據,第4行是調用被測試函數,這些容易理解,不作進一步解釋。第5行是預期輸出,它的特點是當實際輸出與預期輸出不同時自動報錯,ASSERT是VC的斷言宏,也可以使用其他類似功能的宏,使用測試工具進行單元測試時,可以使用該工具定義的斷言宏。

  示例中的格式顯得很不簡潔,2、3、4、5行可以合寫爲一行:ASSERT(pObj->Add(0, 0) == 0);但這種不簡潔的格式卻是老納極力推薦的,因爲它一目瞭然,易於建立多個測試用例,並且具有很好的適應性,同時,也是極佳的代碼文檔,總之,老納建議:輸入數據和預期輸出要自成一塊。

  建立了第一個測試用例後,應編譯並運行測試,以排除語法錯誤,然後,使用拷貝/修改的辦法建立其他測試用例。由於各個測試用例之間的差別往往很小,通常只需修改一兩個數據,拷貝/修改是建立多個測試用例的最快捷辦法。

 

測試用例設計

 

下面談談測試用例設計。前面已經說了,測試用例的核心是輸入數據。預期輸出是依據輸入數據和程序功能來確定的,也就是說,對於某一程序,輸入數據確定了,預期輸出也就可以確定了,至於生成/銷燬被測試對象和運行測試的語句,是所有測試用例都大同小異的,因此,我們討論測試用例時,只討論輸入數據。

  前面說過,輸入數據包括四類:參數、成員變量、全局變量、IO媒體,這四類數據中,只要所測試的程序需要執行讀操作的,就要設定其初始值,其中,前兩類比較常用,後兩類較少用。顯然,把輸入數據的所有可能取值都進行測試,是不可能也是無意義的,我們應該用一定的規則選擇有代表性的數據作爲輸入數據,主要有三種:正常輸入,邊界輸入,非法輸入,每種輸入還可以分類,也就是平常說的等價類法,每類取一個數據作爲輸入數據,如果測試通過,可以肯定同類的其他輸入也是可以通過的。下面舉例說明:

  正常輸入

  例如字符串的Trim函數,功能是將字符串前後的空格去除,那麼正常的輸入可以有四類:前面有空格;後面有空格;前後均有空格;前後均無空格。

  邊界輸入

  上例中空字符串可以看作是邊界輸入。

  再如一個表示年齡的參數,它的有效範圍是0-100,那麼邊界輸入有兩個:0和100。

  非法輸入

  非法輸入是正常取值範圍以外的數據,或使代碼不能完成正常功能的輸入,如上例中表示年齡的參數,小於0或大於100都是非法輸入,再如一個進行文件操作的函數,非法輸入有這麼幾類:文件不存在;目錄不存在;文件正在被其他程序打開;權限錯誤。

  如果函數使用了外部數據,則正常輸入是肯定會有的,而邊界輸入和非法輸入不是所有函數都有。一般情況下,即使沒有設計文檔,考慮以上三種輸入也可以找出函數的基本功能點。實際上,單元測試與代碼編寫是“一體兩面”的關係,編碼時對上述三種輸入都是必須考慮的,否則代碼的健壯性就會成問題。

  白盒覆蓋

  上面所說的測試數據都是針對程序的功能來設計的,就是所謂的黑盒測試。單元測試還需要從另一個角度來設計測試數據,即針對程序的邏輯結構來設計測試用例,就是所謂的白盒測試。在老納看來,如果黑盒測試是足夠充分的,那麼白盒測試就沒有必要,可惜“足夠充分”只是一種理想狀態,例如:真的是所有功能點都測試了嗎?程序的功能點是人爲的定義,常常是不全面的;各個輸入數據之間,有些組合可能會產生問題,怎樣保證這些組合都經過了測試?難於衡量測試的完整性是黑盒測試的主要缺陷,而白盒測試恰恰具有易於衡量測試完整性的優點,兩者之間具有極好的互補性,例如:完成功能測試後統計語句覆蓋率,如果語句覆蓋未完成,很可能是未覆蓋的語句所對應的功能點未測試。

  白盒測試針對程序的邏輯結構設計測試用例,用邏輯覆蓋率來衡量測試的完整性。邏輯單位主要有:語句、分支、條件、條件值、條件值組合,路徑。語句覆蓋就是覆蓋所有的語句,其他類推。另外還有一種判定條件覆蓋,其實是分支覆蓋與條件覆蓋的組合,在此不作討論。跟條件有關的覆蓋就有三種,解釋一下:條件覆蓋是指覆蓋所有的條件表達式,即所有的條件表達式都至少計算一次,不考慮計算結果;條件值覆蓋是指覆蓋條件的所有可能取值,即每個條件的取真值和取假值都要至少計算一次;條件值組合覆蓋是指覆蓋所有條件取值的所有可能組合。老納做過一些粗淺的研究,發現與條件直接有關的錯誤主要是邏輯操作符錯誤,例如:||寫成&&,漏了寫!什麼的,採用分支覆蓋與條件覆蓋的組合,基本上可以發現這些錯誤,另一方面,條件值覆蓋與條件值組合覆蓋往往需要大量的測試用例,因此,在老納看來,條件值覆蓋和條件值組合覆蓋的效費比偏低。老納認爲效費比較高且完整性也足夠的測試要求是這樣的:完成功能測試,完成語句覆蓋、條件覆蓋、分支覆蓋、路徑覆蓋。做過單元測試的朋友恐怕會對老納提出的測試要求給予一個字的評價:暈!或者兩個字的評價:狂暈!因爲這似乎是不可能的要求,要達到這種測試完整性,其測試成本是不可想象的,不過,出家人不打逛語,老納之所以提出這種測試要求,是因爲利用一些工具,可以在較低的成本下達到這種測試要求,後面將會作進一步介紹。

  關於白盒測試用例的設計,程序測試領域的書籍一般都有講述,普通方法是畫出程序的邏輯結構圖如程序流程圖或控制流圖,根據邏輯結構圖設計測試用例,這些是純粹的白盒測試,不是老納想推薦的方式。老納所推薦的方法是:先完成黑盒測試,然後統計白盒覆蓋率,針對未覆蓋的邏輯單位設計測試用例覆蓋它,例如,先檢查是否有語句未覆蓋,有的話設計測試用例覆蓋它,然後用同樣方法完成條件覆蓋、分支覆蓋和路徑覆蓋,這樣的話,既檢驗了黑盒測試的完整性,又避免了重複的工作,用較少的時間成本達到非常高的測試完整性。不過,這些工作可不是手工能完成的,必須藉助於工具,後面會介紹可以完成這些工作的測試工具。

編輯本段單元測試工具

  現在開始介紹單元測試工具,分別按編程語言進行分組介紹。

C/C++

  CppUnit

  首先是CppUnit,這是C++單元測試工具的鼻祖,免費的開源的單元測試框架。由於已有一衆高人寫了不少關於CppUnit的很好的文章,老納就不現醜了,想了解CppUnit的朋友,建議讀一下Cpluser 所作的《CppUnit測試框架入門》,。該文也提供了CppUnit的下載地址。

  C++Test

  然後介紹C++Test,這是Parasoft公司的產品。[C++Test是一個功能強大的自動化C/C++單元級測試工具,可以自動測試任何C/C++函數、類,自動生成測試用例、測試驅動函數或樁函數,在自動化的環境下極其容易快速的將單元級的測試覆蓋率達到100%]。[]內的文字引自,這是華唐公司的網頁。老納想寫些介紹C++Test的文字,但發現無法超越華唐公司的網頁上的介紹,所以也就省點事了,想了解C++Test的朋友,建議訪問該公司的網站。華唐公司代理C++Test,想要購買或索取報價、試用版都可以找他們。

  Visual Unit

  最後介紹Visual Unit,簡稱VU,這是國產的單元測試工具,據說申請了多項專利,擁有一批創新的技術,不過老納只關心是不是有用和好用。[自動生成測試代碼 快速建立功能測試用例 程序行爲一目瞭然 極高的測試完整性 高效完成白盒覆蓋 快速排錯 高效調試 詳盡的測試報告]。[]內的文字是VU開發商的網頁上摘錄的,。前面所述測試要求:完成功能測試,完成語句覆蓋、條件覆蓋、分支覆蓋、路徑覆蓋,用VU可以輕鬆實現,還有一點值得一提:使用VU還能提高編碼的效率,總體來說,在完成單元測試的同時,編碼調試的時間還能大幅度縮短。算了,不想再講了,老納顯擺理論、介紹經驗還是有興趣的,因爲可以滿足老納好爲人師的虛榮心,但介紹工具就覺得索然無味了,畢竟工具好不好用,合不合用,要試過才知道,還是自己去開發商的網站看吧,可以下載演示版,還有演示課件。

  gtest

   gtest測試框架是在不同平臺上(Linux,Mac OS X,Windows,Cygwin,Windows CE和Symbian)爲編寫C++測試而生成的。它是基於xUnit架構的測試框架,支持自動發現測試,豐富的斷言集,用戶定義的斷言,death測試,致命與非致命的失敗,類型參數化測試,各類運行測試的選項和XML的測試報告。需要詳細瞭解的朋友可以參閱《玩轉Google單元測試框架gtest系列》該篇文章。

Java

  JUnit

  JUnit 是 Java 社區中知名度最高的單元測試工具。它誕生於 1997 年,由 Erich Gamma 和 Kent Beck 共同開發完成。其中 Erich Gamma 是經典著作《設計模式:可複用面向對象軟件的基礎》一書的作者之一,並在 Eclipse 中有很大的貢獻;Kent Beck 則是一位極限編程(XP)方面的專家和先驅。JUnit 設計的非常小巧,但是功能卻非常強大。JUnit ——是一個開發源代碼的Java測試框架,用於編寫和運行可重複的測試。他是用於單元測試框架體系xUnit的一個實例(用於java語言)。主要用於白盒測試,迴歸測試。

  JUnit的好處和JUnit單元測試編寫原則:

  好處:可以使測試代碼與產品代碼分開;針對某一個類的測試代碼通過較少的改動便可以應用於另一個類的測試;易於集成到測試人員的構建過程中,JUnit和Ant的結合可以實施增量開發;JUnit是公開源代碼的,可以進行二次開發;可以方便地對JUnit進行擴展;

  編寫原則:是簡化測試的編寫,這種簡化包括測試框架的學習和實際測試單元的編寫;是使測試單元保持持久性;是可以利用既有的測試來編寫相關的測試;

  有興趣的朋友可以下下來仔細研究下。

  JUnit-addons

  對JUnit的一些補充,比如設置、獲取被測試對象的私有屬性的值,調用被測試對象的私有方法等。

  常用類:junitx.util.PrivateAccessor

  Spring 測試框架

  可以測試基於Spring的應用,通過配置文件和註解自動組裝需要的單元測試對象。

  提供了一些常用的J2EE Mock對象,比如HttpSession的Mock類等。

  可以支持數據庫自動回滾,以防止對數據庫的單元測試(插入,刪除等)不可重複執行,防止修改數據庫狀態等。

  DJUnit

  通過代碼自動產生Mock對象,省去了自己手動編寫N多的Mock類。

  此外,它的Eclipse插件還可以做到測試覆蓋率、分支統計。

  EasyMock

  功能同DJUnit,也是通過編程自動Mock掉與測試對象無關的類,方法。

發佈了9 篇原創文章 · 獲贊 4 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章