2012年4月22日 星期日

還是關於 TDD, 以及 Mock

自從上次 TDD 寫得太差造成 team member 的困擾之後, 我最近寫程式都嚴格遵守 Dependency Inversion Principle ─ 當我的 class 需要用到 database 或 要呼叫 remote service 等等的外部資源時, 我會根據此 class 的需求訂出 interface, 這樣一來我的 class 就不會和外部資源有直接的關聯 ─ unit test 時就寫個 fake object 來 implement 這個 interface.

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

沒有留言:

張貼留言