????Go 單元測試git
????1.單測工具github
????2.單測golang
????2.1 調本身框架
????2.2 直接mock遠程調用接口ide
????2.3 monkey函數
????3.優雅的單測工具
// go mock相關: go get github.com/golang/mock/gomock go get github.com/golang/mock/mockgen //stub相關: go get github.com/prashantv/gostub // monkey go get github.com/bouk/monkey // goconvey go get github.com/smartystreets/goconvey
在單元測試過程當中,遇到了兩個問題,第一個:單元測試
func Target() { A() } func A() { // call rpc interface }
如今咱們想測試Target函數,可是因爲調用的A函數依賴於本身的某個函數,這裏就是A調用了rpc接口拉別人接口數據,咱們想mockA接口的目標是,想直接拿到A返回的數據便可,直接採用gomock方式,行不通,本身測試了一下,發現要不斷的mock 別人接口所依賴的其餘接口,很是麻煩,經過注入代碼或者後面第三種方式替換函數便可解決。測試
實踐中只須要按照下面方法來,注入一個getHook函數,在test裏面纔去賦值:code
var getHook func([]string) ([]Info, error) func Target() { if getHook != nil { info, err = getHook("xxx") } else { info, err = handler.getInfo("xxx") } }
其中返回的數據時Info:
type Info struct { ID string, }
test文件中,這樣寫:
func mockGetInfo(x []string) ([]Info, error) { res := []Info{ { ID: "xxx" }, } return res, nil } func Test_xxx(t *testing.T) { // 注入屏蔽測試 getHook = mockGetInfo // inject getHook("xxx") }
這樣,就能夠把這個接口給mock掉了。
在代碼中,還會有調別人的服務,例如:雙方約定Pb rpc協議來調用拉取數據,現有下面這個接口:
type Service interface { GetSerData(req *SerReq) (rsp *SerRsp, err error) }
主函數調用以下
// 請求渲染後臺 som := NewServiceClientProxy(opts...) // 發起rpc調用 rsp, _ := som.GetSerData(&req)
這個就比較簡單了,直接採用gomock+gostub便可解決,不須要注入代碼及主邏輯,很是方便!
首先,使用mockgen生成相應mock_service.go
mockgen -destination=mocks/mock_service.go -package=mocks com.gcx Service
該命令中解釋以下:
destination表示生成的目標文件
package表示上述文件的包名
com.gcx表示mock的接口包名
Service表示接口名
使用gostub對proxy進行打樁,能夠簡單理解位用本身的替換代碼中想mock的接口。
ctrl := gomock.NewController(t) mockedService := mocks.NewMockServiceClientProxy(ctrl) serStubs := gostub.Stub(&NewServiceClientProxy, func(opts ...client.Option) Service { return mockedService }) defer serStubs.Reset()
隨後,咱們想經過本身的mock本身想要的數據,只須要下面這樣描述預期行爲便可:
mockedService.EXPECT().GetSerData(gomock.Any(), gomock.Any(), gomock.Any()).Return(&SerRsp{ // 填充字段 }, nil).AnyTimes()
使用monkey測試,算是最簡單的一種方式了,不用本身去打樁,而後替換,也不用像方法1同樣進行主邏輯的函數注入,mock誰,咱們就替換掉這個方法或者函數就好了,而mockey就是這麼直接的。
首先看一下安裝問題:正常的 方式爲:
import "github.com/bouk/monkey"
源碼指定了 import
方式,所以實際單測中應該:
import "bou.ke/monkey"
此時,須要進入gopath裏面:go/pkg/mod/github.com/bouk
,重命名文件夾:mv github.com/bouk bou.ke
如何去使用呢,下面舉個例子:
假設要測試getNum:
func getNum() { // dosomething handler := &ProcessHandler{} unionInfo, err = handler.GetSerData("xxx") // dosomething } // 咱們想mock掉該接口,該接口具體實現以下: func (s *ProcessHandler) GetSerData(c []string) ([], error) { // dosomething return info, err }
此時咱們直接使用
patch
進行替換便可:
var s *ProcessHandler monkey.PatchInstanceMethod(reflect.TypeOf(s), "GetSerData", func(o *ProcessHandler, x []string) ([]union.CoverInfo, error) { res := []Info{ { ID: "xxx" }, } return res, nil })
mockey是有一些坑的,例如:你要測是的函數或者方法不可導出,就會報下面錯誤:
這裏以GetSerData不可導出爲例:panic: unknown method GetSerData反射機制的這種差別致使了Monkey框架的缺陷:在go1.6版本中能夠成功打樁的首字母小寫的方法,當go版本升級後Monkey框架會顯式觸發panic,表示
unknown method:
具體patch的原理見後面參考。
vscode生成的單測,以下:
func Test_getNum(t *testing.T) { tests := []struct { name string args args wantErr bool }{ { name: "TestGetNum", args: args{ req: &SerReq{ }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := getNum(tt.args.req); (err != nil) != tt.wantErr { t.Errorf("getNum error = %v, wantErr %v", err, tt.wantErr) } }) } }
太挫了,來看一下高富帥的:
convey.Convey("getNum invoke test", t, func() { type args struct { req *SerReq } input := args{ ctx: ctx, req: &SerReq{ }, } err := getNum(req) convey.So(err, convey.ShouldBeNil) })
這裏引入convey,具體安裝見前面。