【RSpec】Mocks

--

https://pixabay.com/photos/sunrise-jetty-lake-pier-sky-1634197/

mock 指的是模擬出相似的事物去替代真實存在的東西。實務上一個類別通常會繼承其它類別,或是與其它物件有所互動,為了降低測試的不確定性,我們會使用 mock 模擬功能中其它需要的物件,藉以避免目標以外的資料影響測試結果、降低除錯難度。

double

double 可以創造一個擁有簡單功能的虛擬 instance 供測試使用。

或是乾脆直接通知測試接受它:

這樣就可以創造簡單的 instance 去完成功能中必要但並非檢測重點的邏輯。

檢測 double

在實務中我們常在類別中引用其他類別創造的 instance 去完成某些功能,使用 double 可以降低程式除錯的複雜度。反過來說,想要確認 double 模擬的 instance 是否有正常運作,也可以用 expect 語法來測試:

expect(member).to receive(:say_follow_me)

如果想要確認方法在類別中被觸發的次數,可以在後面接上 exactly:

expect(member).to receive(:say_follow_me).exactly(1).times

此時若方法被觸發超過一次,測試就會失敗。

當然也可以限制方法被觸發的最高次數:

expect(member).to receive(:say_follow_me).at_most(5).times

與 exactly 不同,at_most 在該方法被觸發小於五次的情況下,測試都會通過。

也可以測試方法的最小觸發次數:

expect(member).to receive(:say_follow_me).at_least(3).times

allow method

如前文所述,allow method 可以將方法與返回值送入假資料中,簡化非檢測重點的必要資料創建過程。值得一提的是,allow method 支援複數回傳值,當我們為一個方法建立複數回傳值時,測試會按照順序回傳答案直到最後一個數值,當呼叫的次數超過設定值時,就以最後一個設定值為唯一回傳值。

如圖所示,因為只給定四個回傳值,用完之後第五個開始便都是固定回傳最後一個設定值。

with method

double 可以使用 receive 為虛擬實體創造方法,但是有些方法可以根據傳入的參數不同而給予不同對應的回應,以 first 這個方法為例:

Member.first # 取出第一筆資料
Member.first(10) # 取出前十筆資料

相同的方法但返回值不同。這時就可以使用 with method 來虛擬參數。

如果希望超過某個值就只回傳這麼多的話(例如 member 的數量只有 5 個,所以參數大於 5 的時候其實全部都會抓出來)也可以這樣設定:

allow(member).to receive(:first).with(be >= 5).and_return([member, member, member, member, member])

這樣就可以操作擁有多型的方法。

instance double

在開發的過程中,常會因為功能的需求改寫或增刪類別內的某些方法,為了使測試資料更貼近現實,可以使用 instance_double 這個方法來創造資料。

let(:member) { instance_double(Member) }

與 double 不同的是,instance_double 會真的去檢查在類別中是否有對應的方法以及 receive 進去的方法參數等相關數值是否符合真正的程式設定,這可以幫助我們在盡量不偏離現實狀況太遠的情境下進行有效測試。

class double

有 instance double,當然也就有 class double,用法與 instance double 類似,不多贅述。

spy

spy 是另一種與 double 有點不同的 mock 執行方式,double 是接收假定的訊息製作出一個符合現實狀況的假想模組,並在測試中檢視是否有接收成功,spy 的做法雖然很像,但在測試時,我們會先呼喚方法,再檢測是否有呼喚成功。

前面介紹過用來驗證 double 的方法都可以用在 spy 上,double 與 spy 沒有好壞之分,端看使用情境決定如何操作比較適合。

相關練習檔案
double_spec.rb
spec/controllers/welcome_spec.rb
movie_spec.rb
allow_method_spec.rb
matching_argument_spec.rb
instance_double_spec.rb
class_double_spec.rb
spies_spec.rb
spies_II_spec.rb

--

--

No responses yet