在從事物件導向程式設計時,有時候我們會運用一種特殊的物件,它的介面和另一個正宗的物件一樣,卻提供了一些特別的用途。我們可以說這種物件模仿了另一個物件的長相,不過它們有著特定的目的。有趣的是,在這一類專司模仿的物件中,還可依據不同目的,細分為多類,我們接下來將介紹兩種有趣又有用的模仿型物件。

可以先行快速實作,填補未完成物件空缺的Dummy Object
首先便是所謂的「Dummy Object」。Dummy有假的、不真實,甚至還有一點笨笨的意思。基本上,Dummy Object和它所模仿的本尊物件,雖然長相一樣,但不像本尊有著各種真實的動態行為。它的行為反應都很簡單,而且也很固定。讓我們看一個例子:

public class Boss implements IBoss
{
 public void think() { …. // 認真的思考、做決策}
 public String wordsToSay() { … // 隨情境說出各種不同的話 }
}
public class DummyBoss implements IBoss
{
&nbsppublic void think() { // 什麼事都不做}
&nbsppublic String wordsToSay() { return “你好,你好”; }
}


DummyBoss是個模仿Boss的Dummy Object,它和Boss有共通的介面,也就是IBoss。但是,正牌的Boss,其think()函式有真實的實作,但冒牌貨DummyBoss則否,它甚至只有一個空的實作。而在wordsToSay()函式的實作上,Boss會經過一些計算過程,回傳不同情境下該說的話,而DummyBoss總是像天線寶寶,只會說出「你好,你好」。

好啦,那既然Dummy Object這麼不聰明,為什麼我們還會需要它呢?有一個十分實用的方式,便是運用它來做提前的整合。

在開發階段,有時候某個模組的開發會相依於另一個模組的完成,例如,模組A中的某個類別,會運用到模組B中的另一個類別,而模組A和B在開發上卻是同步進行,因此,模組A中的該類別,便有可能需要等待模組B的完成,才能繼續撰寫,這麼一來,這兩個模組的開發,就沒有辦法完全得到分頭進行在時間上的優勢。存在相依性的模組在整合上,很多時候我們可以運用Dummy Object,讓存在相依性模組之間的整合,提前發生。

Dummy Object雖然傻傻的,沒有辦法如正牌貨一般發揮十足的能力,但是起碼介面長相和正牌貨一模一樣。當平行開發的兩個模組間存在相依性時,可以先利用Dummy Object來替代那些仍在平行開發中的類別,這使得開發進度可以不受影響,繼續向前邁進。雖然Dummy Object可能只有很制式、甚至很固定的函式行為,但是,它所提供的介面可都是貨真價實的,讓你的程式不僅可以編譯,甚至還可以得到確切的反應。Dummy Object中用來提供制式反應的函式,一般來說也被稱為「Method Stub」。

因為Dummy Object的實作內容很簡單,份量也輕,所以可以很快就實作完成來填補未完成之正牌物件的空缺。等到正牌物件問世後,便可以把Dummy Object退除役,換上正牌物件登場、開始真正的整合。這便是這類物件可以平行化開發動作的作用。

替代原始物件,為受測程式提供行為反應的Mock Object
還有一類物件,同樣模仿正牌物件的介面長相,它的反應相對而言,卻複雜多了,這一類的物件便是所謂的「Mock Object」。它可以允許外界,透過一些方式來控制它所模仿之正牌物件的行為。

為什麼會需要Mock Object?通常,程式設計者會運用Mock Object來測試其他物件的行為是否正確。你可以在一些單元測試的程式碼中,見到Mock Object的蹤影。

在進行單元測試時,受試的物件可能會需要和一些其他的物件互動,但是這些物件可能有一些不確定的行為,例如,其反應可能和時間有關,但是單元測試時,可能會希望測試到各種時間條件,那麼,在單元測試中直接使用這樣的物件,便無法讓單元測試有一個確切可以預期的結果。

舉例來說,在你的單元測試中,想要測試每月結束時系統該執行的功能是否正確。程式碼中可能會倚靠一個物件來取得系統時間,以判斷是否到了應該執行該功能的時機。若在單元測試中,仍倚靠同一個物件來判斷這個時機,那麼這個單元測試,就必須一直等到月底才能執行完畢,這顯然是不可能。

又例如,在單元測試中,可能會需要測試程式在網路斷線時的反應是否如預期。但是,單元測試一般都是自動化進行,不可能在測試時,利用人工的方式去切斷網路連線。這些,都是在進行單元測試時,因為測試的需要而衍生出來的困難。

在月結功能的例子中,倘若我們可以利用另一個物件來提供受測程式是否到了該執行功能的時間點,那麼,透過控制這個物件,讓它可以依照單元測試程式的需求,告訴受測程式是否該執行月結功能,那麼,便可以避掉這種不確定的情況。

而在網路斷線處理測試的例子中,如果程式中是倚靠某個物件來提供網路斷線與否的資訊時,那麼便可以在單元測試中,透過另一個同樣可以被單元測試程式控制其行為的物件來告訴受測程式目前網路的連線狀態,那麼,也可以避免網路斷線情境不易產生的測試困擾。

這種用來替代原始物件,在可受控制的情況下,為受測程式提供各種因應測試需求而需要之行為反應的物件,便是所謂的Mock Object。

要怎麼運用Mock Object呢?本尊物件可能有一個叫做isTodayMonthEnd()的函式,它的實作方式,便是取得系統時間,接著判斷當天是否是一個月的最後一天。所以,倘若在單元測試中,直接呼叫本尊物件的isTodayMonthEnd()時,函式的回傳值是無法預期的,因為它和執行時的當下時間有關,這麼一來,就不能很確定的在單元測試程式中,分別檢驗程式在一個月最後一天、以及不是在一個月最後一天的行為是否符合預期。

我們可以撰寫一個Mock Object,它也同樣提供了一個名為isTodayMonthEnd()的函式,但是,我們再額外為它多加了一個叫做isMonthEnd的boolean屬性,而在Mock Object中的isTodayMonthEnd()實作,便直接回傳此屬性之值。原來的本尊物件並沒有這個屬性可供設定,但是Mock Object版本有,其目的便是要允許單元測試程式,可以藉由設定這個屬性來控制是否滿足月底的條件。當單元測試程式想要測試受測程式在月底的行為是否正確時,便可以將此屬性設為true;反之,可以設為false。如此一來,便可以輕易地透過控制Mock Object,來為受測程式提供確定的不確定因素(意指執行時間不確定,但透過Mock Object便可控制)。

冒牌貨也有實用性
不論是Dummy Object或是Mock Object,都不是真實的物件,它們只是和正牌貨長的一模一樣的冒牌貨。但是,即使如此,它們也能提供真實不虛的實質作用:適當運用Dummy Object可以加快開發的腳步,妥當使用Mock Object,則可以協助單元測試的進行。兩者可說是十分實用,而它們也再度印證了物件導向設計的技巧,可以發揮多大的作用。

專欄作者


熱門新聞

Advertisement