- Published on
Course - TDD Day 01
開發常見的問題
- 測試環境無法測試
- 是誰作錯了
- 測過那些東西
- 改東壞西
- 功能平行開發
Unit Test 相對應的解決方式
- 使用 Mock 作模擬
- 線上問題即是測試案例
- 交付時要含測試程式
- 隨時執行測試
- Isolated 關注點分離
什麼是 Unit Test
- 最小的測試單位
- 一般書上是用"粒度"來形容
- 外部相依性為零
- 不需要網路,不用有 db 也可以執行
- 不具備邏輯
- 不會有 if、else、for loop
- 因為沒有邏輯所以寫很快
- 一般人寫 Unit Test 遇到的問題:寫 Unit Test 時都是把 PRD Code 的邏輯搬到測試程式,自己會疑問為什麼要測自已的程式 ?? 為什麼同樣的邏輯為什麼要寫兩次 ??
- 測試案例之間相依性為零
- 如果有相依的話會不知道測試之後是錯在那一個 Unit Test
- 一個測試案例只測一件事
- 可以寫很多 assert 但是只測一件事
剛開始導入 Unit Test 的原則
- 初期可以複製貼上
- 隨時丟掉
- 測試程式可以用中文
Unit Test 特性 FIRST
Fast
- 正常執行時間要低於 500ms,不可超過一秒
Independent
- 沒有相依性,應該是被隔離的
Repeatable
- 不管跑幾次出來的結果應該都是一樣的
Self-Validating
- 測試程式應該要說是成功、失敗、錯誤,如果跑完測試還要去其它地方看結果,這就有問題
Timely
- 要有即時性,寫完 prd 的 code 就要寫完 unit test
測試案例最難的在於搞不清楚需求
測試程式的 hello word 就是寫加法器的 Unit Test
Unit Test 3A 原則
Arrange
- 初始化目標物件
- 初始化方法參數
- 建立模擬物件行為
- 設定環境變數期望結果
Act
- 實際呼叫測試目標物件的方法
Assert
- 驗證目標物件是否如同預期運作
- 一般來說要測的測試類別會命名成
target
,要測試的結果會命名成actual
開始寫 Unit Test 時
- 命名 Method,可以看到 Method Name 就知道在測試什麼
- 先把 3A 寫上去
- 先想好要測什麼東西,要怎麼去描述它
- 寫測試程式
MS Test 相關簡介
- Test 標記
TestClass
和TestMethod
- 要有標示才會跑
- 驗證
Assert
CollectionAssert
ExpectedException
在驗證什麼情況之下要丟出什麼 exception
- Hook
ClassInitialIze
- 開始會跑一次ClassCleanup
- 所有的測試方法跑完時TestInitialize
- 跑測試程式時TestCleanup
- 跑完測試方法時- 一般來說在作資料的初始化跟清除時才會用到
TestContext
一般來說不會用- 測試相關 Attribute
TestCategory("tag")
- 在裡面可以寫 tag,就可以在在測試總管裡面使用 Group By tag 的功能
Ignore
- 測試總管還是看的見,只是不會跑
- 使用時機:先寫好測試程式,沒有寫主程式,CI 在 Build 時就會出錯,這個時候就要使用
- 熱鍵
- Ctrl + R T 執行單一測試
- Ctrl + R, Ctrl + T 偵錯單一測試
- Ctrl + R A 執行所有測試
- Ctrl + R, Ctrl + A 偵錯所有測試
AreSame 會去判斷記憶體的位置 AreEqual 會去呼叫 Object 本身的 Equals 方法,一般來說如果要驗證 Object 的話就要自已去定義什麼情況之下會相等
一次只測一件事
- 這樣子不管成功還是失敗,才能知道測試案例代表的意義
- 一個測試案例測兩件事,測試失敗時就有兩懂清況
主要的三種測試
- 驗證回傳值
- 驗證狀態的改變
- 驗證測試與外部的互動 - Mock
Independent (Isolated) 為什麼需要隔離?
- 執行速度快
- 關注點分離
- 單一職責
- 獨立測試 (可測試性)
- 測試程式的健壯性 (Robustness)
一般來說 Unit Test 的中斷點通常加在 actual 上面
當 UnitTest 有問題時
- Clear Project
- Rebuild Project
- 清除 DLL
Isolated Unit Test - 依賴注入
直接相依的問題
- 直接 new 起來用就叫相依
- 測試可能因為相依而變慢
- 網路、資料庫...甚至相依另一個深不見底的物件
- 為了可重複執行,需要初始/還原
- 相依物件有問題時,會導致測試目標物件的錯誤
- 你不殺伯仁,伯仁卻因我而死
- 要等相依物件開發完成,才能測試目標物件
- 開發/測試效率低落
- 問題發現時間點延後,成本提升
- 無法測試
Unit Test 相對應解決相依問題
- 單一職責 SPR
- 抽出相依的 class
- 依賴介面
- 從使用端角度定義介面
- 相依的 class 實作此介面
- 依賴注入 DI
- 從建構式注入相依介面的實體
- 從公開 property 注入相依介面的實體
- 測試程式中自行定義 stub
Isolated Unit Test
手刻 stub 類別的問題
- 煩悶無聊
- 生產力低落
- 不知道為什麼要這麼麻煩
使用 Rhino.Mocks 產生 stub 物件
實務上的作法
- PRD Code 不能 new class
- 相依的 class 都要有 interface
- 把 new 的動作放在工廠方法裡面
- 導入 DI
- 不用 static
Mock 很白箱,會跟著邏輯跑
Stub 跟 Mock 不要同時驗證
字串看起來一樣不過執行不一樣,就有可能是特殊字元
Unit Test 之前 比較重要的是怎麼作 IoC
測試程式就是 需求的說明 跟 用法的說明
production Code 不能測試,表示沒有彈性
不要為了測試而測試,測試是描述需求、功能的一部份
是否需要針對非 public 方法撰寫單元測試 ?
單元測試與物件導向封裝的本質
- 單元測試的意義
- 摸擬外部如何使用測試目標物件,驗證其行為是否符合預期
- 物件導向的封裝原則
- 隔離出物件的內部與外部,也就是定義物件的邊界,以及定義外部可視部份
- 把外部使用端不需要了解物件內部的資訊封裝起來,就是隱藏物件的實作細節
- 把物件內容的變化封裝起來,怎麼變化都不會影響外面的使用
直接測非 public 方法,有什麼問題
- 為了測試而測試,而不是以需求為出發點
- 原本物件應該被封裝起來讓外部不受影響,卻因測試程式測 private 方法而導致不具健壯性
- 測試涵蓋率會失去一定意義
測試涵蓋率 Code Coverage
測試涵蓋率的意義
- Code Coverage 不足
- 測試案例不足
- 存在著與需求無關
測試涵蓋率的建議
- 至少要大於 0%,代表該產品存在相關的測試程式
- 檢視測試案例是否包含最主要的情境,以及曾經出錯的情境
- 檢視是否有不必要的產品程式碼或缺少對應的測試案例