【go的测试】单测之gomock包与gomonkey包

目录

使用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 infra

func 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.go

func 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 infra

func 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.go

func 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 infra

type 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.go

func 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 infra

type 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.go
 
func 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 的第一个参数是成员的类型,第二个参数是函数名称,第三个参数是预期的行为函数(注意入参的第一个参数必须是成员对象)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值