單元測試 – Code Coverage的意義

如何用Code Coverage來衡量單元測試的成果

  • 在build的時候要自動去跑測試(使用CI,CD當作工具)> 大於0%
  • 關心相對趨勢 大於 絕對數字,衡量相對的數字,評估code coverage的數值有沒有比昨天高,每一次針對PROD的程式碼修改都要加上測試,這樣這個數值只會往上不會往下。鼓勵持續的COMMIT > 持續整合,每天都要COMMIT個5~6次或7~8次。
  • 那些測試的投資報酬率高:為什麼要寫測試?目標是要提升產品品質。假設這段PROD CODE沒有BUG,其實不需要為它寫測試。那什麼東西的投資報酬率最高?
    • 1. 要修正的BUG(BUG越晚發現修正成本就更高)
    • 2. 實務上常跑到的scenario
    • 3. 最主要的情境
    • 4.和錢有關的
    • 5.和人命有關的(EX:自動駕駛系統)
    • 6. 最常改到的CODE,可以避免被改錯
  • 那code coverage的意義如下:
    • 觀察沒有被含蓋到的情境:判斷要不要補測試
    • Dead Code:根本就不會跑到的Code(代表這一段PROD CODE不會跑到)

最好導入的方式

從上面看來,兩個最好導入的方式:1. 針對所有BUG的修改去寫測試,2. 針對新的專案去寫測試
然後要確認code coverage不可以往下掉。
假如現在有一陀爛CODE,要增加新功能在那堆爛CODE裡,先把爛CODE抽成method,然後再抽成新的Class
針對爛CODE的public的情境先寫測試,會讓需要寫測試的範圍變小很多。

測試的品質

  • 測試的程式一定要重構
  • 測試的語意一定要清楚明白,一般Assert都會抽出成一個func,這是為了讓測試更容易理解,可以很簡單的的從測試的程式碼由語意就能理解這個測試要做什麼
  • 寫測試的難度會反應程式的好壞

單元測試 – 依賴注入

使用依賴注入來達成低藕合高內聚的程式碼

1. 針對相依的物件抽出interface,針對相依的值抽出field
2. 產生contructor,選要注入的field去依賴注入
3. 產生無參數的constructor,確保本來的程式無誤
4. 測試程式新增fake物件做interface,決定行為並注入SUT

以下為範例程式
AuthenticationServiceTests.cs

AuthenticationService.cs

使用Substitute來針對不同狀況實作假介面


上面做法有什麼問題?

  • 每一個不同的依賴案例就要製作一個不同的FakeObject,會讓寫測試的時間太久
  • 沒辦法直接從程式碼知道為什麼這樣會是Vaild

使用Subsitute(NSub)

定義假物件行為(stub)

使用mock object assertion

需求:驗證是非法的時候要記一個log

不要用太多mock就算要使用要避免過度指定,也就是當prod code小小變動就導致測試程式壞掉

驗證傳入的參數是否包含某些關鍵字

測試替身

  • stub:不做驗證,單純只做模擬相依物件的行為
  • mock:一開始就要把所有的都定義清楚,應該要呼叫那個方法,所有的值一定要一模一樣,否則就會報錯(嚴格,敏感,不穩定)
  • spy: 則是把所有的互動先做完,只驗要驗的,剩的沒有測就是都算過,差異是一個從嚴一個從寬。(寬鬆)

因此mock和spy本身含有驗證(Assertion),而stub本身只有在模擬相依的物件而已。

重構test

  • 抽出field: mock object, stub
  • Setup: SUT初始化(或者[TestInitialize])
  • 抽出方法(用Given為開頭):定義mock object行為,代表假如在跑這個scenario時…
  • Extra Method with “Shouldxxx()” => SUT行為+Assertion

3A pattern: Arrange, Act, Assert
上面重構後Arrange就用Given…,然後Act就是SUT行為,Assert就是Assertion。

下面為重構後的程式碼:

單元測試 – 寫測試的基本原則

熱鍵

  • Alt+Enter: Quick+Action
  • Ctrl+R,M 抽方法出來
  • code snippets / live template
  • constructor: ctor
  • property:prop
  • console.writeLine)():cw
  • ctrl+R,F: Extract Field
  • Alt+Insert
  • 循環剪貼簿:ctrl+shift+V

Extrait and Override


這一個測試的重點在於:針對依賴的code,找出不可控制的地方並抽出來

  • 找到不可控制的依賴
  • 抽出方法
  • 把private改成protect virtual
  • 在測試專案新增子類繼承SUT
  • override protected方法並加開set方法
  • 測試SUT改測子類
  • set依賴的值

Holiday.cs

Test2Cs.cs

比較物件屬性的方式

比較兩邊的物件包含子物件完全相同

比較以expected為主的屬性去比較相對應的actual是否相同

測試範例如下

寫測試的規則



測試程式不含商業邏輯,所有的測試都應該是直述句
不應包含以下的元素:

  • prod business logic
  • 不含if, else, switch case等邏輯程式碼
  • 更不含try..catch
  • 不含for, while, foreach, do..while

善用assertion package

  • C#: expectedObjects,FluentAssertions
  • Java: Assert J

不要攤開properity做比較

寫測試一定要重構,不然在測試需求異動時和寫測試時會太花時間
因此不應該要有太多不會用到的資訊
讓測試的目標的意圖可以很明顯
要如何加快單元測試撰寫的速度很重要,這樣才有可能可以實踐單元測試
沒有時間是個問題,但是我們要去面對如何解決這個問題
要知道怎麼用工具怎麼寫比較快