Mock / Spy
模擬一段測試過程
測試過程中總會有一些流程必須跳過,像是寄信、打外部 API 等,略過這些流程的方法就是 Mock/Spy。
先舉一個例子,這是一個常見的類別,裡面有 __construct
和 execute
方法:
這個類別相依了 Handler
、 $mailer
和 User
的物件,在使用的時候會像這樣:
如果直接跑測試,就真的會寄信了。所以先示範一下如何 mock:
以上述範例來說,handler
和 mailer
是可以被 mock 的。但 new User(3)
無法被替換。這種 new class
的方式會造成功能的依賴耦合(Dependency),但 Laravel 有提供 app
可以隨時隨地的反轉依賴(Dependency inversion)。這種作法還有個好處,可以避免 __construct
寫太多東西:
如果要 mock Laravel 依賴注入這寫法,就必須要這樣寫:
Mock vs Spy
Mock 與 Spy 最大的差異是先後驗證,Mock 的驗證會寫在 arrange
,Spy 驗證會寫在 assert
階段上。兩者的程式碼也差在 Spy 多了 shouldIgnoreMissing(),Spy 會在執行階段時忽略全部並回傳 null,如果 Spy 的物件需要回傳值就需要這樣寫:
就如剛剛所說的,Spy 與 Mock 只差一個方法。所以 mock 也是可以使用 shouldHaveReceived
,但 mock 通常會使用 shouldReceive
先行驗證,所以沒有必要驗證兩次,除非 mock 是使用 expects
來模擬行為。
如果希望所有驗證都寫在 assert
區塊,就使用 Spy。
我是覺得被 mock 的東西只是為了執行測試的手段過程,我就不會特別去寫驗證來證明有測過他,因為整個 I/O 都可能是假的。
不要 Mock/Spy
使用 Mock/Spy 可以更容易的寫好測試,但用得越多,整個測試就會越假,越偏離實際使用。像上面的範例,我可以把所以外部物件全都 mock 一遍,但就只能測到哪些物件有沒使用,而不能保護當中的物件如果遇到更改需求時能不能反應異常。
像是 TemplateHandler
的 getTemplate
本來是不需要參數的,但有天有個需要帶入第一個參數,這時測試還是會過。因為 mock/Spy 把整個物件取代掉了,所以不會知道這個物件的實際需求。
除非要做局部的測試(只測試目標的特定流程,故可以快速忽略前面的需求)或排除需要網路或外部服務的功能,否則不推薦濫用它們。
Last updated