例如這個很奇怪的例子, 我要寫的 function 是能把所有傳入的 integer 加總然後存到某個 Database 裡. 下面這個 unit test 就是在驗證這件事. 這個例子中我特地寫了一個 FakeDB ─ 他會去紀錄 caller 到底傳了甚麼 value 進來到 SavedSum 這個 property 裡 ─ 而 unit test 就是驗證 SavedSum 果然如預期就是 1+2+3+4.
[TestMethod] public void TestThattSumOfAllParamsAreSavedToDB() { FakeDB db = new FakeDB(); Class1 c1 = new Class1(db); c1.sum(1, 2, 3, 4); Assert.AreEqual(1 + 2 + 3 + 4, db.SavedSum); } class FakeDB : DBInterface { public int SavedSum { get; private set; } public SaveValue(int value) { this.SavedSum = value; } } interface DBInterface { void SaveValue(int value); } class Class1 { private DBInterface db; public Class1(DBInterface db) { this.db = db; } public void SumToDB(params int[] values) { int total = 0; foreach (int value in values) total += value; this.db.SaveValue(total); } }
但是建立這些 FakeDB, FakeServer, FakerService, FakeFileSystem, Fake巴拉巴拉是很耗時耗力又很難 maintain 的, 最好得方法是改用專們產生 fake object 的 mock framework 來幫忙.
下面就是使用 Moq 來建立 mock object 的例子. 簡單很多吧!
public void TestThattSumOfAllParamsAreSavedToDB() { Moq.Mock<DBInterface>db = new Moq.Mock<DBInterface>(); db.Setup(x => x.SaveValue(1+2+3+4)).Verifiable(); Class1 c1 = new Class1(db.Object); c1.sum(1, 2, 3, 4); db.VerifyAll(); }
我的小感想: 我用 TDD 好一陣子了但一直沒用 mock framework, 所以造成我過去寫 unit test 時, 都是硬做自己的 fake object. 寫太多 fake object 就覺得好費時又好費力, 最後變成乾脆不切這些 interface 了, 直接在 test fixture 裡設定 db / http server 之類的, 因為覺得這樣反而比寫 fake object 還快. 最終的結果就是如之前提到的反而造成同事困擾. 現在我會用 mock framework 了, 又開始感覺到 TDD 的順暢感跟爽度了. TDD 使用者們, 若你還沒開始用 mock framework, 快去找一個吧!