golang單元測試之mock
搞單元測試,如果碰到這些情況:
1,一個函式,內部包含了很多並且很深的呼叫,但是如果單單測這個函式,其實實現的功能很簡單。
2,一個函式,包含了其他還未實現的呼叫。
3,函式內部對資料的要求極為苛刻。
那麼這時候就可以考慮使用mock來處理。
mock,簡而言之就是可以通過注入我們所期望返回的資料,或者我們所期望傳遞的引數,來避免上面那些情況,其原理則是通過反射來實現。
這次就來看看golang的mock,gomock
gomock是go官方提供的mock解決方案,主要分為兩部分:gomock庫和mock程式碼生成工具mockgen。
使用舉例:
package metal
type Imetal interface {
GetName() string
SetName(string) string
}
type Metal struct {
Name string
Exchange string
}
func (self Metal) GetName() string {
if self.Name==""{
return "none"
}
return self.Name
}
func (self *Metal) SetName(brand string) string {
self. Name=brand
return "done"
}
我現在有一個package,其包含了IMetal介面,這個介面下面有兩個方法,現在針對這兩個方法來進行mock,ps:gomock只支援interface方法的mock。
在mock之前,需要先通過mockgen來生成mock程式碼,我的源就是上面的IMeta介面。
簡單介紹一下mockgen:
它有兩種工作模式---source和reflect
source模式
mockgen -source=foo.go [other options]
根據原始檔來生成,原始檔是包含了一個或多個interface的檔案。
reflect模式
mockgen src/package Conn,Driver
一個檔案定義了多個interface而你只想對部分interface進行mock,或者interface存在巢狀,使用reflect模式
mock程式碼生成好之後,接下來是寫測試函式。
package metal
import (
"mock_metal"
"github.com/golang/mock/gomock"
"testing"
"fmt"
)
func GetMetalName(mi Imetal) string {
mi.GetName()
return mi.GetName()
}
func SetMetalName(mi Imetal,name string) string {
return mi.SetName(name)
}
func TestMetalName(t *testing.T) {
mockCtl := gomock.NewController(t)
defer mockCtl.Finish()
mockMetal := mock_metal.NewMockImetal(mockCtl) //mock_metal就是生成的mock程式碼,以包的形式存在
m:=new(Metal)
mockCtl.RecordCall(m,"GetName").Times(1)
mockCtl.Call(m,"GetName")
call:=mockMetal.EXPECT().GetName().Return("apple")
mockMetal.EXPECT().GetName().Return("peer").After(call) //注入期望的返回值
mockedBrand:=GetMetalName(mockMetal)
mockMetal.EXPECT().SetName(gomock.Eq("al")).Do(func(format string) { //入參校驗
fmt.Println("recv param :",format)
}).Return("setdone")
mockMetal.EXPECT().SetName(gomock.Any()).Do(func(format string) { //入參不做校驗
fmt.Println("recv param :",format)
}).Return("setdone")
mockedSetName:=SetMetalName(mockMetal,"al")
fmt.Println(mockedSetName)
if "peer"!=mockedBrand{
t.Error("Get wrong name:", mockedBrand)
}
if "setdone"!=mockedSetName{
t.Error("Set wrong name:", mockedSetName)
}
}
然後執行go test即可,會發現這些被mock的函式會按照我們定義的行為來執行。
附帶兩個gomock的官方資料,基本上看完了就可以上手了。
第一個是gomock庫的所有方法說明,第二個是官方的例子,裡面有如何進行gomock的方法使用。