開發的過程要寫程式,寫程式我們都知道要寫測試,寫測試的時候經常看見 Stub, Dummy, Mock 等等這些名詞。使用 Testing Framework 的時候,更是經常使用到看起來功能類似,不同名稱的物件。稍微理解這些名詞的概念,使用測試框架的時候會有點幫助。
首先,這是看了 xUnit Patterns 網站裡面的部分頁面,寫下的筆記。如果有看不懂的地方,請連回去直接看原文,或是用這當關鍵字之一去搜尋其他人的文章。
接著來一些會反覆出現的名詞解釋
名詞#
SUT System Under Test,就是待測物。舉例來說,當我們在寫 Unit Test,SUT 往往就是 Class,那個我們要測試的 Class
DOC Depended-On Component,SUT 所依賴的元件,也就是 Mock, Stub ..想要抽換掉的元件。把這些相依性抽換成我們可以控制的狀態,以專注去測試 SUT。
test fixture 測試的時候特意準備的東西,讓測試可以順利跑完所需要,有些人會叫做 test context。舉例來說,一組測試用的資料。
control point 用 control point 來操作 SUT,有些 control point 只是為了測試而存在。舉例來說,某個 class 開了純為測試存在的 API,這種 control point 當然不能在產品裡面使用。
indirect input 改變 SUT 的數值,並非直接來自我們的操作,而是來自於 DOC。好比我們對一個物件呼叫
updateName,然後物件去問另外一個資料庫物件,取得了在資料庫裡面的數值來更新自己indirect output 當 SUT 的一些狀態改變了,而我們沒有辦法透過直接的 API 去取得改變的狀態。可是,它同時也會影響到其他元件的狀態,後面這些能被觀察的狀態,就是 indirect output。這與後面的 Mock object 有關。
本文的重點,我看的書有將其翻譯為「測試副本」。這個字不好翻譯,double 當作名詞有「替身」的意思,Test Double 就是我們要拿來替換掉真實運作的 DOC 的物件們,得到一個更簡單,可以測試的環境。
大致能夠把 Test double 分成以下幾種
測試的過程,可以用這張圖大概解釋一下
- Setup,生成 Test double 來取代 DOC,並準備好 Fixture 方便接下來的測試
- 初始化 SUT,通常就是建構一個 object
- Exercise,執行測試。這時候 SUB 還可能會去跟 Test double 去要一些數值
- Verify,確認執行完的結果是否無誤
- Teardown,把之前測試過程產生的東西給清乾淨
有了這個流程,接下來要解釋 Test double 就簡單多了
Test Stub#
Test stub 就是,提供 indirect input 給 SUT 的 Test double
舉例來說,我們要測試登入用的程式是不是有正常運作。SUT 就是登入用的主程式,塞了一個假的 Network 物件給他,只要對 Network 物件下 GET 總是回傳 200 OK。這個 Network 物件就是 Stub。
Test Spy#
Test spy 就是能力更強,有記錄功能的 Test Stub,提供 indirect input 給 SUT 的 Test double
以前面的例子來說,在驗證的階段,可能還想要知道這個 Network 物件的 API 被呼叫了幾次,有沒有使用到錯的 end-point?能夠告訴我們這些資訊的,就是 Test Spy。
Mock Object#
Mock Object 就是帶有判斷功能的 Stub,判斷 SUT 是不是正確地使用這個 DOC
前面的 Spy 是看最後有沒有正確的 indirect output。但也有可能,最後的結果是對的,中間的順序錯了。好比說呼叫 web api 的順序是不是正確的?這時候我們需要在 Test double 裡面偷看實作的運作。Mock Object 就會在被使用的過程中,擁有檢查的邏輯。
Fake Object#
Fake Object 就是提供比較簡單、輕量實作的 DOC
Fake Object 不在意 Indirect input 也不在意 indirect output,只專心地滿足自己的介面。
前述的幾個物件,要嘛是專門提供假的固定值(Stub),要嘛是有檢查的功能。Fake 物件就是一個簡化過 DOC,有著一樣的介面但是實作都很簡單,譬如說是個 In-memory 的資料庫物件,用起來像是真正的資料庫,但是操作沒有 disk IO,而且資料可能是我們寫好的 fixture。
Dummy Object#
用來填充的無用物件,只是為了滿足 API 的介面
建構一個物件,或是呼叫一個方法的時候,有時候需要傳入一些物件當作參數,而我們知道這些物件完全不會被使用到,但是為了要滿足介面,成功編譯,塞進去的填充物就是 Dummy Object。