Course - TDD Day 01

Posted on 2014-08-23

開發常見的問題

  • 測試環境無法測試
  • 是誰作錯了
  • 測過那些東西
  • 改東壞西
  • 功能平行開發

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 時

  1. 命名 Method,可以看到 Method Name 就知道在測試什麼
  2. 先把 3A 寫上去
  3. 先想好要測什麼東西,要怎麼去描述它
  4. 寫測試程式

MS Test 相關簡介

  • Test 標記
    • TestClassTestMethod
    • 要有標示才會跑
  • 驗證
    • 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 的話就要自已去定義什麼情況之下會相等

一次只測一件事

  • 這樣子不管成功還是失敗,才能知道測試案例代表的意義
  • 一個測試案例測兩件事,測試失敗時就有兩懂清況

主要的三種測試

  1. 驗證回傳值
  2. 驗證狀態的改變
  3. 驗證測試與外部的互動 - 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%,代表該產品存在相關的測試程式
  • 檢視測試案例是否包含最主要的情境,以及曾經出錯的情境
  • 檢視是否有不必要的產品程式碼或缺少對應的測試案例

Web Test 使用 Selenium + FluentAutomation

好課程請參考 SkillTree