目錄
使用gomock包
1.?安裝mockgen
2. 定義接口
3.?生成mock文件
4. 在單測中使用mock的函數
5. gomock 包的使用問題
使用gomonkey包
1. mock 一個包函數
2. mock 一個公有成員函數
3. mock 一個私有成員函數
使用gomock包
1.?安裝mockgen
go get -u github.com/golang/mock/gomock
go get -u github.com/golang/mock/mockgen
2. 定義接口
比如我們想要 mock 一個 infra 包中的名為 GetFromRedis 的函數:
// pkg/infra/infra.go
package infrafunc GetFromRedis(cli *redis.Client, key string) (string, error) {// 為什么這里要打印一下呢,因為一行會觸發內聯優化,影響單測結果...fmt.Println("cannot reach here if is ut")return cli.Get(context.Background(), key).Result()
}
那需要定義一個 interface 將此GetFromRedis 的函數封裝起來:
type Redis interface {GetFromRedis(cli *redis.Client, key string) (string, error)
}
這個 interface 必須定義,否則 gomock 無法生成 mock 方法。
3.?生成mock文件
在命令行執行:
mockgen -source=pkg/infra/infra.go -destination=pkg/infra/mock/mock_infra.go -package=mock
其中 source 為待 mock 的文件,destination 為 mock 文件生成的位置。
mockgen 工具將為你生成?pkg/infra/mock/mock_infra.go 文件。
4. 在單測中使用mock的函數
mock 就可以這樣寫:
// pkg/infra/infra_test.gofunc Test_gomock(t *testing.T) {// 創建gomock控制器ctrl := gomock.NewController(t)defer ctrl.Finish()var redisCli *redis.Client// 創建模擬對象m := mock.NewMockRedis(ctrl)// 定義預期行為m.EXPECT().GetFromRedis(redisCli, "test_key"). // 模擬入參Return("test value", nil) // 模擬返回值// 調用被測方法,獲取實際結果resVal, err := m.GetFromRedis(redisCli, "test_key")assert.NoError(t, err)assert.Equal(t, "test value", resVal)
}
5. gomock 包的使用問題
- 用起來很麻煩,要定義 interface,還要執行 mockgen 命令
- 無法 mock 私有成員的函數
使用gomonkey包
當你用了 gomonkey 之后!就再也不會想用 gomock 了!(gomonkey打錢)
1. mock 一個包函數
還是上述例子,比如我們想要 mock 一個 infra 包中的名為 GetFromRedis 的函數:
// pkg/infra/infra.go
package infrafunc GetFromRedis(cli *redis.Client, key string) (string, error) {fmt.Println("cannot reach here if is ut")return cli.Get(context.Background(), key).Result()
}
無需前置操作,直接在單測中寫 mock 代碼:
// pkg/infra/infra_test.gofunc Test_gomonkey_function(t *testing.T) {// 創建gomonkey對象, 模擬GetFromRedis預期行為patch := gomonkey.ApplyFunc(GetFromRedis, func(_ *redis.Client, _ string) (string, error) {return "test value", nil})defer patch.Reset()// 調用被測方法,獲取實際結果resVal, err := GetFromRedis(&redis.Client{}, "test_key")assert.NoError(t, err)assert.Equal(t, "test value", resVal)
}
ApplyFunc 的第一個參數是被測函數,第二個參數是預期的行為函數(注意入參出參定義要與被測函數完全一致)
當然你還可以用 patch 去 mock 更多函數:
patch.ApplyFunc(...)
2. mock 一個公有成員函數
比如我們有如下公有成員函數?PublicRedisHandler.GetFromRedis:
// pkg/infra/infra.go
package infratype PublicRedisHandler struct {cli *redis.Client
}func NewPublicRedisHandler(cli *redis.Client) *PublicRedisHandler {return &PublicRedisHandler{cli: cli,}
}func (r *PublicRedisHandler) GetFromRedis(key string) (string, error) {fmt.Println("cannot reach here if is ut")return r.cli.Get(context.Background(), key).Result()
}
在單測中寫法為:
// pkg/infra/infra_test.gofunc Test_gomonkey_public_member(t *testing.T) {// mock 公共成員方法patch := gomonkey.ApplyMethod(reflect.TypeOf(&PublicRedisHandler{}), "GetFromRedis", func(_ *PublicRedisHandler, key string) (string, error) {return "test value", nil})defer patch.Reset()// 調用被測方法,獲取實際結果r := NewPublicRedisHandler(&redis.Client{})resVal, err := r.GetFromRedis("test_key")assert.NoError(t, err)assert.Equal(t, "test value", resVal)
}
其中?ApplyMethod 的第一個參數是成員的類型,第二個參數是函數名稱,第三個參數是預期的行為函數(注意入參的第一個參數必須是成員對象)
3. mock 一個私有成員函數
那如果我們想要 mock 的函數是一個私有成員下的函數呢:
// pkg/infra/infra.go
package infratype privateRedisHandler struct {cli *redis.Client
}func newewPrivateRedisHandler(cli *redis.Client) *privateRedisHandler {return &privateRedisHandler{cli: cli,}
}func (r *privateRedisHandler) GetFromRedis(key string) (string, error) {// 為什么這里要打印一下呢,因為一行會觸發內聯優化,影響單測結果...fmt.Println("cannot reach here if is ut")return r.cli.Get(context.Background(), key).Result()
}
那么寫法為:
// pkg/infra/infra_test.gofunc Test_gomonkey_private_member(t *testing.T) {// mock 私有成員方法patch := gomonkey.ApplyPrivateMethod(reflect.TypeOf(&privateRedisHandler{}), "GetFromRedis", func(_ *privateRedisHandler, key string) (string, error) {return "test value", nil})defer patch.Reset()// 調用被測方法,獲取實際結果r := newPrivateRedisHandler(&redis.Client{})resVal, err := r.GetFromRedis("test_key")assert.NoError(t, err)assert.Equal(t, "test value", resVal)
}
其中?ApplyPrivateMethod 的第一個參數是成員的類型,第二個參數是函數名稱,第三個參數是預期的行為函數(注意入參的第一個參數必須是成員對象)