第207期 / January 5, 2015

研發新視界

分享到臉書!分享到維特!分享到噗浪!分享到Google+!分享到微博!轉寄友人友善列印

使用單元測試以提升程式碼品質之心得分享

作者/李沛承

[發表日期:2015/1/5]

前言

什麼是單元測試
  • 定義:單元測試就是用來模擬外部如何使用測試目標物件,驗證其行為是否符合預期
  • 測試你的程式碼是否OK的一種方式
  • 從程式最小的功能開始
  • 單元指的是一個類別或一個模組
單元測試就是驗證程式可以跑得正確的方法。那怎麼證明?從程式的最小功能開始,比如說一個呼叫,做兩個整數的加法1+1=2,最小功能點就是一個單元。

測試的目的
  • 怎樣讓不同Layer間程式可以平行開發,不用等別人寫好底層元件就可以測試
  • 程式發生錯誤,怎麼模擬出當時狀況及Debug?
  • 改了這個function,會不會導致別的程式掛掉?
  • 到底測過了哪些功能,有沒有漏掉的還沒測?
  • 提早發現缺陷、提高軟體品質、產生可靠的程式碼…等。
以程式碼的測試為例,單元測試 (Unit Test) 就是一種由程式開發人員負責測試程式碼每一段邏輯是否合乎預期的工作,簡單來說就是希望在每一個測試的方法當中,皆有簡單且明確的意義,來證明某一項功能在某一個case下程式是如有按照預期運作。

由於目的是完整測試,想當然還得花費一些人力來完成這些測試的工作,開發時程增加 (約30%~50% overhead),對於有時程、人力限制的軟體專案,單元測試並不是很容易推行的,那推行單元測試到底有什麼好處呢?
  • 怎整體專案時程縮短
  • 提昇程式碼/設計/測試品質
  • 節省測試與除錯時間,更容易找bug
如果系統程式碼有著非常完整的單元測試個案,那之後的維護問題是否會比較少?不過要推行單元測試的一個大前提,那就是必須簡化單元測試開發流程,降低人力花費,盡可能自動化。

自動化測試
  • 介面測試(UI Testing)
  • 單元測試(Unit Testing)
  • 整合測試(Integration Testing)
簡單說就是透過程式或軟體去進行測試,沒有人的介入去進行測試。舉例來說,字母o與數字0,如果單純打”o”或者”0”一般很容易造成誤判,像是常用的一組密碼組合是 w0rd,透過程式的判斷”w0rd”是字串並不會判斷錯誤,但人工的方式很容易輸入為”word”。自動化測試就是要去除人工介入而可能產生的錯誤,進而提升測試的可信度、可靠度。

單元測試(UNIT TESTING)

這是這次執行測試的專案結構

《圖一》

以下是單元測試的起始範例,介紹如下:
  • TestClass:用來標記這是個單元測試的類別
  • TestMethod:用來標記這是個單元測試的方法
  • 必須是public, 沒有參數且無回傳值的方法



《圖二》單元測試範例

使用Assert Class這種方法可以用來評估測試結果,評估結果為false會丟出例外(AssertionException)主要方法為以下幾種
    1. AreEqual()
    2. AreSame()
    3. IsFalse() / IsTrue()
    4. IsNull() / IsNotNull()
    5. Inconclusive()

1.Assert.AreEqual()用途
  • 比較結果是否與預期值相同
  • Value type的比較(非object)
  • 當失敗時顯示錯誤訊息與預期結果
以AreEqual為範例,首先在測試專案下建立一個TestMethod,接著創造一個被測專案中的Class LogonInfo以便執行被測專案中的更改密碼method ChangePassword。範例如下
單元測試成功範例:


《圖三》Assert.AreEqual的範例

點開上方的功能表【測試(S)】→【視窗(W)】→【測試總管(T)】打開測試總管


《圖四》

這時可以在想執行的method按右鍵選擇→【執行選取的測試】,如《圖五》;也能以左鍵點選上方的【全部執行】或者點選【執行】後再選取其他功能像是【執行失敗的測試(F)】、【執行未執行的測試(N)】等,如《圖六》;此例子是執行《圖五》的步驟


《圖五》



《圖六》

以下則為測試結果,可以看到TestChangePasswordCorrect的測試結果是成功的。


《圖七》

由測試結果可發現使用Assert.AreEqual來判斷密碼是否由被測專案中的method改為”NewPasswprd12345678”的測試結果為”測試成功”。

單元測試失敗範例:
這個例子故意在更改密碼時讓密碼出錯,在由單元測試來測試其結果為何


《圖八》



《圖九》

由測試結果可發現使用Assert.AreEqual來判斷密碼是否由被測專案中的method改為”NewPasswprd12345678”的測試結果為”測試失敗”,下方還會列出預期值以及實際值的不同,然而可以發現失敗的原因是因為更改密碼時只給了”NewPasswprd1234567”,所以失敗了!
由於測試的結果大同小異,Assert剩下的範例就以直接show程式碼用法來代替了。

2.Assert.AreSame()用途:
  • 評估2個變數是否指向同一個object instance
  • 等於Object.ReferenceEquals



《圖十》

3.Assert.IsFalse / IsTrue ()用途:
  • 評估運算式的結果是否為 false / true



《圖十一》

4.Assert.IsNull / IsNotNull用途:
  • 評估某變數是否為 null / not null



《圖十二》

5.Assert.Inconclusive()用途:
  • 用來表示該測試尚未實作
  • 拋出 AssertInconclusiveException例外並停止目前測試
  • 測試結果非成功也非失敗


《圖十三》

6.ExpectedException Attribute用途:
  • 用來指出測試結果應該為某種型態例外
  • 必須產生該例外測試結果才算pass


《圖十四》


程式碼涵蓋率分析

在測試專案中,評估單元測試的品質可以用程式碼涵蓋率來作為一個重要的指標。那程式碼涵蓋率是什麼呢?簡單來說,就是目前的所有單元測試所涵蓋到的受測程式碼與全體受測程式碼的比例。舉個例子,如果受測的程式換算為程式碼總共有100行,但所有的單元測試呼叫執行到70行,則程式碼覆蓋率為百分之70。程式碼涵蓋率越高代表單元測試所測試到的受測程式越多,也表示目前的單元測試能夠測試出程式錯誤的可能性越高,因此,通常會以程式碼涵蓋率作為單元測試品質的指標。

點開上方的功能表【測試(S)】→【分析程式碼涵蓋範圍(C)】→【所有測試】就會得到所有的測試在程式碼涵蓋範圍的比率


《圖十五》



《圖十六》


結論

單元測試有助於在系統尚未開發完成時可以獨立出來測試、確認程式的正確性,最理想的狀況當然是全系統都有單元測試,則可以大幅提升程式碼品質且更容易抓出系統上的漏洞並且修改它,也提供程式開發人員一個安全的開發環境,在開發的每個部分都能確實達到預期的結果以預防讓bug延續下去導致整個系統的毀壞,同時幫助別人更快速的維護與理解程式,進而提升開發的彈性與設計,同時也因為對於程式的確定性更高了所以客戶提出變更的可行性也跟著提高,而不至於因為要新增修改某項功能而導致程式的不穩甚至破壞,因此可以提供客戶更好的服務及信賴度。系統品質對於專案是最重要的,因此如何讓專案成員有更多的時間花費在測試系統的所有面向上是很重要的課題。

參考資料

1. Visual Studio 2010 單元測試 (中)
2.軟體測試(SOFTWARE TESTING)超快速入門筆記
3.[Day 3]動手寫Unit Test