跳至主要内容

📋 第一章 單元測試的基礎

  • 定義一個單元測試
  • 對比單元測試與集成測試
  • 探索一個簡單的單元測試示例
  • 理解測試驅動開發

1.1 逐步定義單元測試

  • 單元測試:提高程式品質,更加理解類別與方法的功能需求
  • 工作單元:從呼叫公用方法到一個測試可見的最終結果,此過程稱之為工作單元
  • 定義:
    1. 一個單元測試示一段代碼(通常是一個方法或一個函數),由此方法去呼叫另一段方法,然後檢驗某些假設的正確性,如果假設是錯誤,表示單元測試失敗
    2. 主專案的程式稱為:被測試系統 (System Under Test SUT),有的人喜歡稱為 CUT(Class Under Teat)
    3. 測試最終結果的形式:
      • 被呼叫的公共方法返回一個值(一個返回值不可為空的函數)
      • 方法被呼叫的前後,系統的狀態或行為有可見的變化,這種變化無須查詢私有狀態即可判斷(ex:一個以前不存在的用戶可以登入系統)
      • 呼叫一個不受控制的第三方系統,這個系統不會返回任何值,或返回值被忽略(ex:呼叫 log4Net,代碼不是你寫的,你也沒源碼)
    4. 單元測試最小可以小到一個方法,大可以包括實現某個功能或多個函數。

1.1.1 編寫優秀單元測試的重要性

  • 要理解單元測試,光理解工作單元還不構
  • 如果編寫差勁的單元測試是沒意義的,還會造成日後維護的麻煩

1.1.2 我們都寫過單元測試

  • 或許你曾經使用過控制台程序來調用一個類別或組件,來驗證最終結果是否能讓你確信代碼工作是正常的
  • 這些測試或許很有用,而且也很接近傳統的單元測試

1.2 優秀單元測試的特性

  • 一個單元測試應具有以下特徵:
    • 自動化,且可以重複執行
    • 很容易實現
    • 第二天還要有意義
    • 任何人都應該能一鍵運行他
    • 結果需要是穩定的(運行之前沒有進行任何修改的話,多次運行一個測試結果應該要是不變的)
    • 要能完全控制被測試的單元
    • 需要是完全隔離的(獨立於其他程式的運行)
    • 如果執行失敗,我們要很容易地知道什麼是期待的結果,進一步定位問題所在
  • 很多人將單元測試的概念與軟體測試的行為混為一談,如果要澄清這問題。首先要先回顧自己以前寫過的測試,並問自己問題:
    • 以前寫的一個單元測試,今天還能順利運行嗎
    • 以前寫的測試,團隊裡有任何一個人可以運行他並得到結果嗎
    • 我能一鍵運行過所有的單元測試嗎
    • 我能在幾分鐘內寫出一個基本的測試嗎
  • 如果以上答案任何一個為否定,那很可能就不是單元測試,實際上是集成測試

1.3 集成測試

  • 集成測試:任何測試,如果運行速度不快,結果不穩定,或者要用到被測試單元一個或多個真實依賴物,(ex:一個測試要使用真實的系統時間,真實的文件或真實的資料庫)

  • 如果一個測試不能控制系統時間,每次都要使用 DateTime.Now,那麼每次執行都是使用不同的時間,因此測試的本質都是不同的,這測試就不穩定了

  • 集成測試與單元測試具有相同的地位都非常重要,但兩種測試應該被被分開來

  • 如果一個測試使用的是真實資料庫,那麼和那些只使用內存中的偽數據測試相比:

    1. 測試痕跡難以消除
    2. 運行時間相對更長
    3. 如果有成千上萬個測試,每分每秒都是關鍵
  • 大多人都是通過用戶介面的最終功能來測試軟體的功能,單擊按鈕會觸發一系列的事件,如果最終結果失敗,那所有這些組鍵做為一整體就失敗了,也很難找出整體操作的失敗

  • 集成測試定義:

    1. 一個循序漸進的測試,軟硬體相結合進行測試直到整個系統集成在一起
    2. 對一個工作單元進行測試,這個測試對工作單元沒有完全控制,並使用該單元的一個或多個真實依賴物
    3. 集成測試會使用真實依賴物,單元測試則把被測試單元和其依賴物隔開,以保證測試結果高度穩定

與自動化單元測試相比,非自動化集成測試的缺點

  • 我兩周前寫的一個單元測試,今天還能運行並得到結果嗎?更久之前的呢?
    • 如果答案是"否",那或許已經破壞之前的功能了
    • 無法對之前所有功能進行測試,就有可能破壞了某個功能而毫不知情,這種情況稱為"偶然引入缺陷"
    • 回歸是以前運行良好但是現在布工作的一個或多個工作單元

1.4 什麼是優秀的單元測試

  • 單元測試最終定義:
    • 一個單元測試是一段自動化的程式,這段程式去呼叫要被測試的工作單元,之後對這個工作單元的結果的某些假設進行檢驗,
    • 容易編寫,能快速運行
    • 可靠可讀容易維護
    • 只要程式不發生改變,測試結果是穩定的

1.5 一個簡單的單元測試範例

  • 有一個 class (SimpleParser)需要被測試,裡面有一個方法(ParseAndSum)
    • ParseAndSum 輸入是由 0 或多個逗號組成的字串陣列
    • 如果輸入的字串不含數值,就回傳 0
    • 如果包含單一一個數值,就將數值轉成 int 回傳
    • 如果包含多個數值,就回傳數值總和的 int
// Define other methods and classes here
public class SimpleParser
{
public int ParseAndSum(string numbers)
{

if(numbers.Length==0)
{
return 0;
}
if(!numbers.Contains(","))
{
return int.Parse(numbers);
}
else
{
throw new InvalidOperationException(
"I can only handle 0 or 1 numbers for now!");
}

}
}

1.6 測試驅動開發

  • 有一個 class (SimpleParser)需要被測試,裡面有一個方法(ParseAndSum)