例如這個很奇怪的例子, 我要寫的 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, 快去找一個吧!