寫 Isolated Unit Test 時很卡嗎?

寫單元測試常碰到的兩種問題:

  1. dependency 的問題,導致無法隔離相依,無法模擬或驗證相依物件的互動
  2. 要模擬的相依物件太多

其實,這都是散發著壞味道的象徵。

當在撰寫 isolated unit test 時發現,要 stub 的物件太多時,往往是兩種 bad smell 的徵兆。

  1. 單一職責的另一種 anti-pattern, 把一個職責過分拆細,導致要完成某一件事得組合太多零碎的相依物件才能運作。(比較常見)
  2. 測試目標物件 (SUT) 的職責過大,需要很多相依物件互動才能完成自己的職責。(機率較小)

另外要留意的一個要點,如果假物件都是 mock, 而非 stub, 那通常代表用錯了。

mock 是透過注入假物件,用來驗證測試目標與這個相依假物件之間的互動,也就是 assertion 的目的。而 stub 物件是用來模擬相依物件的動作,以便讓單元測試能獨立驗證測試目標物件本身的邏輯是否正確。

因為單元測試一次只測一件事,所以請勿同時對測試目標物件進行 assert, 又對 mock 物件進行 assert。因為這兩個 assert 的關注點,勢必是不同件事。

在《單元測試的藝術》一書上提到,mock 跟 stub 在實務上的比例大約是 5%:95%(個人經驗差不多是 10%:90%)。過多過細的 mock, 會讓 test cases 的穩定度 (Robustness) 下降, 孩子生了就得養,所以需要生的時候再生就好。

剛好的 mock, 可以有效降低測試案例幾倍的維護成本。

如果你非得驗證到 mock 物件互動所接收到的參數,請記得「只驗證意義,例如透過 regex pattern。而不是驗證參數的所有細節」

補充

測試案例的撰寫與維護,往往是 production code 設計品質的照妖鏡。如果你碰到以下的狀況,代表:

  1. 一個需求異動,要加好多的測試案例:代表違反單一職責與開放封閉原則,應該只需要新增 class 做切換,而不是對原本內容修改。
  2. 一個需求異動,要改好多測試案例:代表違反單一職責,一個物件的職責太大。
  3. 要 stub 的物件很多:代表違反單一職責,因為同一個職責被拆到太多物件身上,要做一件事就需要很多個物件一起,才能正常運作一件事。
  4. 互動的方式一改、參數一改,測試案例就壞:代表 mock 太深,mock 可以只驗證互動次數、參數的意義以及參數的完整內容,越後面代表綁得越深。
  5. UI 一改,所有測試案例都要跟著修改:layout 與 scenario 耦合性太高,請透過 page object pattern 從測試程式設計上解耦。

 


blog 與課程更新內容,請前往新站位置:http://tdd.best/