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, 快去找一個吧!

2012年4月8日 星期日

關於 user story 的二三事

這兩週我所屬的 happy cloud team 在整理 user story 時學到了點東西, 野人獻曝一下

一定要用完整句子寫下 user story


學習過 user story 怎麼寫的人, 一定知道有些泛用 pattern 常為人所推薦, 例如:


但真正寫起來, 常常會簡化成這樣子 (姑且稱之"關鍵字串"好了):

  • remove user on web site
  • remove user on app
  • user session expired

寫"關鍵字串"真的比寫完整的一句話來得快, 但它致命的缺點其實是會浪費更多時間去解釋這組關鍵字串到底要做甚麼.

選擇題 1.  Remove user on web site 意思是?

  1. A system admin can kill an account on web site
  2. A user can kill his own account on web site
  3. 以上皆非
  4. 以上皆是

選擇題 2.  remove user on app 是說

  1. A user can kill his account using foobar app
  2. A user can make foobar app "un-remember" an account so that he won't see this account in the user list anymore.
  3. 以上皆非

看似平淡的一組"關鍵字串"卻有很多的解釋空間, 一個 iteration planning 免不了要從十數個到數十個 story 中挑一些出來做, 好幾次我們就遇到, 好不容易解釋完這一堆"關鍵字串"究竟要做甚麼, 挑出了個位數的 story 確認要排入 iteration 中了, 當要把 story 再拆成 tasks 時還是會聽到 「ㄟ... 這個 story 究竟是要做甚麼啊?」不過是兩小時的 planning meeting 就會發生這樣的事, 要是整個 product backlog 都用關鍵字串, 那全部解釋完 product owner 大概人也老了.

一定要用中文


同事們和我英文都不差, 但中文都比英文好更多. 描述同樣一個 user story, 用中文我們可以描述得更簡潔更精準更有效率, 既然如此, 那何直接不用中文撰寫 user story? 唯一提醒的是, 有些詞彙也許平常就慣用英文, 例如專業術語甚麼的, 那就不用太費力全翻成中文了, 像台灣霹靂火般的中英夾雜也是種美, 畢竟達到溝通的目的才是我們要的.

中文還有另一個優勢, 同樣的字數, 中文比英文夾帶更多資訊量. 用中文寫同一個 story 本來就會更短, 更容易寫在一張 7.5x7.5 的便利貼上, 甚至字可以寫大一點, 眼睛比較舒服, 這也算好處之一吧 :)