注意:本文大部份內容爲翻譯 Bob 大叔的文章,原文連接能夠在文章底部的參考文檔處找到。html
mock 做爲名詞時表示 mock 對象,在維基百科的解釋中以下:程序員
在面向對象程序設計中,模擬對象(英語:mock object,也譯做模仿對象)是以可控的方式模擬真實對象行爲的假的對象。程序員一般創造模擬對象來測試其餘對象的行爲。web
mock 做爲動詞時表示編寫使用 mock 對象。數據庫
mock 多用於測試代碼中,對於不容易構造或者不容易獲取的對象,使用一個虛擬的對象來方便測試。編程
爲了使用示例說明各個mock 種類的區別與聯繫,文章使用 go 語言做爲示例,以下爲示例的基礎代碼:安全
type Authorizer interface {
authorize(username, password string) bool
}
type System struct {
authorizer Authorizer
}
func NewSystem(authorizer Authorizer) *System {
system = new(System)
system.authorizer = authorizer
return system
}
func (s *System) loginCount() int {
// skip
return 0
}
func (s *System) login(username, password string) error {
if s.authorizer.authorize(username, password) {
return nil
}
return errors.New("username or password is not right")
}
複製代碼
當你不關心傳入的參數被如何使用時,你就應該使用 dummy 類型的 mock,通常用於做爲其餘對象的初始化參數。示例以下:服務器
type DummyAuthorizer struct {}
func (d *DummyAuthorizer) authorize(username, password string) bool {
// return nil
return false
}
// Test
func TestSystem(t *testing.T) {
system := NewSystem(new(DummyAuthorizer))
got := system.loginCount()
want := 0
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
複製代碼
在上面的測試示例代碼中,DummyAuthorizer 的做爲只是爲了初始化 System 對象的須要,後續測試中並無使用該 DummyAuthorizer 對象。網絡
注意:此處的 authorize 方法原文返回了 null ,因爲 go 語言不容許爲 bool 返回 nil ,所以此處返回了 false架構
當你只關心方法的返回結果,而且須要特定返回值的時候,這時候你就可使用 stub 類型的 mock 。好比咱們須要測試系統中某些功能是否能正確處理用戶登陸和不登陸的狀況,而登陸功能咱們已經在其餘地方通過測試,並且使用真實的登陸功能調用又比較的麻煩,咱們就能夠直接返回已登陸或者未登陸狀態來進行其餘功能的驗證。函數
type AcceptingAuthorizerStub struct {}
func (aas *AcceptingAuthorizerStub) authorize(username, password string) bool {
return true
}
type RefusingAuthorizerStub struct {}
func (ras *RefusingAuthorizerStub) authorize(username, password string) bool {
return false
}
複製代碼
當你不僅是隻關心方法的返回結果,還須要檢查方法是否真正的被調用了,方法的調用次數等,或者須要記錄方法調用過程當中的信息。這個時候你就應該使用 spy 類型的 mock ,調用結束後你須要本身檢查方法是否被調用,檢查調用過程當中記錄的其餘信息。可是請注意,這將會使你的測試代碼和被測試方法相耦合,測試須要知道被測試方法的內部實現細節。使用時須要謹慎一些,不要過渡使用,過渡使用可能致使測試過於脆弱。
type AcceptingAuthorizerSpy struct {
authorizeWasCalled bool
}
func (aas *AcceptingAuthorizerSpy) authorize(username, password string) bool {
aas.authorizeWasCalled = true
return true
}
// Test
func TestSystem(t *testing.T) {
authorizer := new(AcceptingAuthorizerSpy)
system := NewSystem(authorizer)
got := system.login("will", "will")
if got != nil {
t.Errorf("login failed with error %v", got)
}
if authorizer.authorizeWasCalled != true {
t.Errorf("authorize was not called")
}
}
複製代碼
mock 類型的 mock 能夠算做是真正的 」mock「 。把 spy 類型的 mock 在測試代碼中的斷言語句移動到 mock 對象中,這使它更關注於測試行爲。這種類型的 mock 對方法的返回值並非那麼的感興趣,它更關心的是哪一個方法被使用了什麼參數在什麼時間被調用了,調用的頻率等。這種類型的 mock 使得編寫 mock 相關的工具更加的簡單,mock 工具能夠幫助你在運行時建立 mock 對象。
type AcceptingAuthorizerVerificationMock struct {
authorizeWasCalled bool
}
func (aavm *AcceptingAuthorizerVerificationMock) authorize(username, password string) bool {
aavm.authorizeWasCalled = true
return true
}
func (aavm *AcceptingAuthorizerVerificationMock) verify() bool {
return aavm.authorizeWasCalled
}
複製代碼
fake 類型的 mock 與其餘類型的 mock 最大的區別是它包含了真實的業務邏輯。當以不一樣的數據調用時,你會獲得不一樣的結果。隨着業務邏輯的改變,它可能也會愈來愈複雜,最終你也須要爲這種類型的 mock 編寫單元測試,甚至最後它可能成爲了一個真實的業務系統。若是不是必須,請不要使用 fake 類型的 mock 。
type AcceptingAuthorizerFake struct {}
func (aas *AcceptingAuthorizerFake) authorize(username, password string) bool {
if username == "will" {
return true
}
return false
}
複製代碼
mock 是 spy 的一種類型,spy 又是 stub 的一種類型,而 stub 又是 dummy 的一種類型,可是 fake 與其餘全部 mock 類型不一樣,fake 包含了真實的業務邏輯,而其餘類型的 mock 都不包含真實的業務邏輯。
根據 Bob 大叔的實踐來看,他使用最多的是 spy 和 stub 類型的 mock ,而且他不會常用 mock 工具,不多使用 dummy 類型的 mock ,只有在使用 mock 工具時纔會使用 mock 類型的 mock 。如今的編程 IDE 中,只須要你定義好接口,IDE 就能夠幫你輕鬆的實現他們,你只須要簡單的修改就能夠實現 spy 和 stub 類型的 mock ,所以 Bob 大叔不多使用 mock 工具。
mock 對象是一個強大的工具,可是 mock 對象也有兩面性,若是使用不正確也可能會帶來強大的破壞力。
若是咱們徹底不使用 mock ,直接使用真實的對象進行測試,這會帶來什麼問題呢?
在徹底不使用 mock 對象的狀況下,咱們的測試會變得緩慢、不完整、脆弱。
若是過分使用 mock 對象,全部的測試都使用 mock 對象,這會帶來什麼問題呢?
過分使用 mock 對象,將會使用測試變得緩慢、脆弱、複雜,而且有可能損壞你的軟件設計。
在架構的重要邊界使用 mock ,不要在邊界內部使用 mock
例如能夠在數據庫、web服務器等全部第三方服務的邊界處使用 mock 。能夠參考以下的整潔架構圖:
能夠在最外環的邊界處使用 mock 隔離外部依賴,方便測試,這樣作能夠獲得以下的好處:
另外一個比較大的好處是它強迫你思考找出軟件的重要邊界,而且爲它們定義接口,這使得你的軟件不會強耦合依賴於邊界外的組件。所以你能夠獨立開發部署邊界兩邊的組件。像這樣去分離架構關注點是一個很好的軟件設計原則。
使用你本身的 mock
mock 工具備它們本身的領域語言,在使用它們以前你必須先學習它。經過前面的 mock 類型介紹,咱們已經知道用的最多的 mock 是 stub 和 spy 類型,而因爲如今的 IDE 能夠很方便的生成這些 mock 代碼,咱們只須要稍做修改就能夠直接使用,因此綜合來看,咱們通常狀況下是不須要使用 mock 工具的。
因爲你本身寫 mock 時不會使用反射,這將會讓你的測試代碼運行速度更快。若是你決定使用 mock 工具,請儘可能少的使用它。
mock 對象既不能徹底不使用,也不能過分使用。咱們應該在軟件的重要邊界處使用 mock ,要儘可能少的使用 mock 工具,使用 mock 工具時不要過分依賴它,咱們應該儘可能使用輕量級的 stub 和 spy 的 mock 類型,而且咱們應該本身手寫這些簡單的 mock 類型。若是你這樣作了,你會發現你的測試運行速度更快,更穩定,而且還會有更高的測試覆蓋率,你的軟件架構設計也會愈來愈好。