一般測試會有三種需要驗證的狀況,最麻煩的物件互動該如何驗證?
該如何設計才能保持測試的獨立性,提高程式的可測性?
三種驗證狀況:
- 驗證目標函式的回傳值
- 驗證目標物件的狀態
- 驗證目標物件是否正確地與其他物件互動
前兩點的實做相對明瞭,在測試框架 MSTest這可以涵蓋大多數的狀況,
那要如何驗證第三種情況呢?
可使用 NSubstitute 當中的 mock 物件:
log.Received(1).Save(Arg.Is
說明:log 這個物件的 Save function 應該要被呼叫1次,且傳入參數為一個 string,
且此 string必須含有 "login failed" 的字樣。
※ Mock 一般驗證的條件較細,會提高維護成本,斟酌使用
測試獨立
為什麼要獨立、要隔離?
- 執行速度快
- 關注點分離
- 單一職責
- 獨立測試(可測試性)
- 測試程式的健壯性(Robustness)
※ 妥妥的,不容易壞啊~
沒獨立產生的問題:
- 測試可能因為相依而變慢
- 相依物件發生問題導致誤判測試的結果
- 等相依物件開發完成,才能測試目標物件
- 無法測試(無法模擬出該測試環境)
如何解決?
- 單一職責
- 依賴介面(interface)
- 依賴注入(控制反轉、Dependency Injection)
將依賴的物件型態給抽象化為 interface,降低物件之間的耦合度
再由外部透過 constructor、property 等方式「注入」 - 測試程式中自行定義 stub 物件
依賴的物件抽象化之後,就可在測試程式中模擬出抽性型別的假資料注入
以便進行「互動」部份的測試。
可使用 NSubstitute 套件,大幅加速 stub 物件的撰寫!
var p = Substitute.For<XXX>();
p.GetPassword("joey").Returns("91");
var t = Substitute.For<XXX>();
t.GetRandom("").ReturnsForAnyArgs("000000")
這部份偶有作假資料的感覺,但因為要測的部份其實「互動」的部份,
假資料並不是測試重點,是相當合理的。
根據個人經驗,Substitute.For<> 比較合適代入 Interface,而非 Class。
官網有提到這部份的訊息:Warning: Substituting for classes can have some nasty side-effects. For starters, NSubstitute can only work with virtual members of the class, so any non-virtual code in the class will actually execute! If you try to substitute for your class that formats your hard drive in the constructor or in a non-virtual property setter then you’re asking for trouble. If possible, stick to substituting interfaces.
http://nsubstitute.github.io/help/creating-a-substitute/
不是為了撰寫 Unit Test 而將程式碼改為 Dependency Injection,是本身程式碼寫的不夠好,
耦合度太高,又缺乏可測性,該重構啊~
耦合度太高,又缺乏可測性,該重構啊~