目錄
- PART I: 入門
- Chap 1: 單元測試基礎
- Chap 2: 第一個單元測試
- PART II: 核心技術
- Chap 3: 透過虛設常式解決依賴問題
- Chap 4: 使用模擬物件驗證互動
- Chap 5: 隔離(模擬)框架
- Chap 6: 深入了解隔離框架
- PART III: 測試程式碼
- Chap 7: 測試階層和組織
- Chap 8: 好的單元測試的支柱
- Chap 9: 在組織中導入單元測試
- Chap 10: 遺留程式碼
- Chap 11: 設計與可測試性
前言
好的單元測試:
- 可信賴
- 可維護
- 易讀
一、撰寫可信任的測試
原則和技術:
- 決定何時刪除或修改測試
- 何時:
- 產品 bug
- 測試 bug
- 語意或 API 有變更(功能不變)
- 矛盾或無效的測試
- 如果測試和產品程式沒有問題,會需要修改的原因
- 重新命名或重構
- 去除重複程式碼
- 何時:
- 避免測試中帶著邏輯
- 不該 包含:
- switch, if, else 語句
- foreach, for, while 迴圈
- 不該 包含:
- 一次只測試一個關注點
- 一個關注點,是一個工作單元的最終結果:
- 回傳值
- 系統狀態的改變
- 對第三方物件的互動
- 否則容易導致
- 難以命名
- 可讀性降低
- 一個關注點,是一個工作單元的最終結果:
- 把單元測試和整合測試分開
- 如上一章節所說的綠色安全區域,讓測試更容易執行
- 推行程式碼審查(確保覆蓋率)
- 程式碼審查:兩個人坐在一起交談,現場查看或修改同一段程式碼
- 並非追求 100% 程式覆蓋率
- 覆蓋率低於 20% 代表缺少很多測試
- 程式碼審查:兩個人坐在一起交談,現場查看或修改同一段程式碼
二、撰寫可維護的測試
- 測試私有或保護的方法
- 讓方法變成公開方法
- 把方法抽取到新類別中
- 把方法改成靜態方法
- 把方法改為內部(internal)方法
- 去除重複程式碼
- 使用輔助方法來去除重複程式碼:如工廠模式
- 使用 [SetUp] 去除重複程式碼
- 具可維護性的設計來使用 Setup 方法:Setup 用起來很容易,但也易導致測試可讀性和可維護性下降
- Setup 濫用
- 初始化只在某些測試中用到的物件
- 冗長難懂的 Setup 方法
- 在 Setup 方法中準備假物件
- 結論:停止使用 Setup 方法,多利用工廠和輔助方法。
- Setup 濫用
- 實作隔離測試:
- 強制的測試順序:必須依照特定順序執行
- 存在呼叫其他測試的隱藏動作:測試呼叫其他測試
- 共享狀態損壞:測試共享記憶體裡面的某個狀態,卻沒有重置
- 外部共享狀態損壞:整合測試共享資源,卻沒有重置
- 避免對不同的關注點進行多次驗證
- 使用參數化測試:[TestCase]
- 使用 try-catch
- 物件比較:
- 提高測試可維護性:建立一個用來比較的物件
- 覆寫 ToString():
public override string ToString() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < this.fields.Length; i++) { sb.Append(this[i]); sb.Append("."); } return sb.ToString() }
- 避免過度指定
- 測試對一個被測試物件的純內部狀態進行驗證
- 測試中使用多個模擬物件
- 測試在需要使用虛設常式物件時,使用模擬物件
- 測試在不必要的情況下,指定順序或使用了精準的參數匹配器
三、撰寫可讀性高的測試
可讀性有以下幾個方面:
- 命名單元測試
- 被測試方法名稱:
- 測試情境:說明了使用測試的條件;「如果我用一個 null 值來呼叫方法 X,那麼它應該執行 Y」
- 預期行為:基於目前的情境,方法應該產生的行為結果或回傳值或行為方式;「如果我用一個 null 呼叫方法 X,那麼它應該執行 Y」
- 如:
MethodUnderTest_Sceneario_Beharvior()
- 被測試方法名稱:
- 命名變數
- 如:
Assert.AreEqual(COULD_NOT_READ_FILE, result)
- 如:
- 使用好的驗證資訊
- 不要重複測試框架的控制命令輸出的資訊
- 不要重複測試名稱裡面包含的資訊
- 如果沒有什麼有用的資訊,就什麼也別說
- 提供關於什麼應該發生和什麼沒有發生的資訊,如果可能的話,提供應該發生的時間點
- 把驗證和操作分開
- Bad Assert
[Test] public void BadAssertMessage() { // 一些程式碼 Assert.AreEqual(COULD_NOT_READ_FILE, log.GetLineCount("abc.txt"); }
- Good Assert
[Test] public void GoodAssertMessage() { // 一些程式碼 int result = log.GetLineCount("abc.txt"); Assert.AreEqual(COULD_NOT_READ_FILE, result); }
- Bad Assert
小結
優秀的單元測試三大支柱:可讀性、可維護性、可信任性。重點是測試要隨著被測試系統一同成長和變化。