Unit Test 中的替身:搞不清楚的 Dummy 、Stub、Spy、Mock、Fake

最近開始接觸單元測試,一堆名詞看不懂,來狠狠地 K 一下。而單元測試中的最佳男配角,就是替身 Double。藉由寫一篇文章的方式來讓自己更了解單元測試中的重要部分。一起來學寫測試,Go Go !!

註:接觸時間還很短,有錯還請不吝指正,如果之後發現我有理解錯誤再回來更正。

單元測試是什麼?

單元測試可以讓你快速驗證程式的行為。了解單元測的話首先來介紹一下 SUT 目標對象。

舉個例子:你想要測試一台車是不是可以開,那麼 SUT 目標對象就是這台車。

為了確保這台車可以開,那麼就可以寫一個「這台車應該要可以開」的測試。如果這台車發生了故障,測試就會告訴你,這台車是壞掉的。

單元測試讓你的程式更容易維護。原因在於你能夠對程式寫測試,代表你寫的程式的「使用說明書」清楚易懂。使用說明書很容易懂,就可以直接拿來用,減少重複的程式碼,符合 DRY (Don’t Repeat Yourself) 原則。

寫程式最大的成本在於維護,寫很簡單,但是要維護可就是個不容易的事情了。為了降低維護的成本,導入單元測試可以大大降低出錯的可能性,並且快樂地重構。

替身是什麼?

下圖是火影忍者的替身之術,替身之術的原理是:當敵人對你發動攻擊時,你使用替身之術,就可以讓替身承受敵人的攻擊,而自己不會受到傷害。圖中的木頭就是呼叫替身之術後,用來承受敵人攻擊的替身。

所以火影忍者替身之術跟 Unit Test 的替身有什麼關係?你在程式中使出替身之術的時候,替身 Double 就會出現在你的程式裡面了。

為什麼要在程式中使用替身 Double 呢?

原因在於你想要測試的 SUT 目標對象通常會有很多「依賴」DOC。一樣舉個例子:剛剛的 SUT 目標對象是「一台車」很容易寫測試,只要測試這台車可以開就好。

但是如果一台車停在停車場,然後又爆胎。或是一台車掉到海裡。這些情況,一台車的 DOC 依賴變多了,造成你的測試更加複雜,越多的 DOC 依賴會造成你的測試越難寫,也造成你的程式行為更加複雜,難以預測。

那麼就進入正題來介紹測試中的幾種替身吧

測試中的五種替身

測試中的 Double 替身分為五種:Dummy Object、Test Stub、Test Spy、Mock Object、Fake Object。先來介紹第一種 Dummy Object 冒牌貨

Dummy Object

Dummy Object 英文直譯冒牌貨,顧名思義就是個冒牌貨。他的用途是用來填充 SUT 目標測試對象中需要的物件。而 Dummy 不會對 SUT 測試目標造成任何的影響。純粹的填充物件,讓測試程式能夠運行。

Test Stub

我們想要驗證的 SUT 回傳值,這時候 Stub 就可以派上用場啦。聽起來有點玄,一樣舉個例子:例如:現在我們的測試目標 SUT 是一個手電桶,我們想要驗證呼叫打開 on 方法的結果,而一個手電筒裡面需要裝電池,電池可能會沒電,但是我不想讓電池沒電這個因素影響我的測試。所以我做了一組替身電池 (Stub Object) 塞進這個手電筒裡面。讓我可以輕鬆驗證 SUT 的回傳值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Flashlight
attr :battery

def initialize(battery)
@battery = battery
end

def on
battery.have_energy? ? 'flashlight is turn on!' : 'no energy'
end

end

Flashlight.new(StubBattery.new).on # 電池有沒有電不會影響結果。

Test Spy

Spy 用來驗證 SUT 與對其他 DOC 物件造成的效果,例如:今天阿牛去找朋友聊天的時候,朋友物件內部的聊天次數就會加一,而當我們想想要驗證朋友物件的聊天次數是不是如我們所預期的增長時,我們就可以派出間諜朋友,來驗證「 SUT 阿牛是否有跟朋友聊三次天」之類的期待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class 阿牛  

def initialize(friend)
@friend = friend
end

def chat_to_friends
friend.chat!
end

end

class Friend
attr: chat_count

def chat
@chat_count ||= 0
@chat_count += 1
end
end

cow = 阿牛.new(spy_friend)

cow.chat_to_friends
cow.chat_to_friends
cow.chat_to_friends

expect(spy_friend.chat_count).to eq(3)

Mock Object

Mock 是一個能夠判斷 SUT 是不是有正確使用 DOC 的替身。Mock 跟 Spy 的最大差別是,Mock 用來驗證 SUT 的行為,而 Spy 用來驗證 SUT 對 DOC 狀態的改變。

Fake Object

Fake Object 假物件是一個簡化的 DOC (依賴元件),例如:一台真實的飛機有很多零件,但是我們其實只需要他有外殼,並且可以飛,可以降落 … 等等的行為。所以做一個簡單版的假物件。假物件不需要考慮跟 SUT 目標對象的間接互動(Indirect input , indirect output)。

其他不錯的資源

歡迎訂閱,獲取最新資訊、教學文章,一起變強。

評論