当前位置: 首页 > news >正文

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

http://www.lryc.cn/news/574261.html

相关文章:

  • 板凳-------Mysql cookbook学习 (十--9)
  • K8S: etcdserver: too many requests
  • Halcon ——— OCR字符提取与多类型识别技术详解
  • Java 程序设计试题​
  • 多智能体协同的力量:赋能AI安全报告系统的智能设计之道
  • Elasticsearch(ES)与 OpenSearch(OS)
  • 苹果芯片macOS安装版Homebrew(亲测)
  • LoHoVLA技术:让机器人像人类一样思考与行动的统一框架
  • AI 智能体架构设计3阶段演进和3大关键技术对比剖析
  • 硬件工程师笔试面试高频考点汇总——(2025版)
  • 最近小峰一直在忙国际化项目,确实有点分身乏术... [特殊字符] 不过! 我正紧锣密鼓准备一系列干货文章/深度解析
  • SpringBoot中使用表单数据有效性检验
  • Ollama 在LangChain中的应用 Python环境
  • RS485
  • Linux运维新人自用笔记(inode索引节点、删除文件原理、raid10、lvm逻辑卷)
  • Python基础(​​FAISS​和​​Chroma​)
  • 十四天机器学习入门——决策树与随机森林:从零构建智慧决策模型
  • 本地文件深度交互新玩法:Obsidian Copilot的深度开发
  • 从Java API调用者到架构思考:我的Elasticsearch认知升级之路
  • RealSense 相机 | 读取IMU | 解决权限问题 | 提供示例程序
  • 用于算法性能预测的 GNN 框架
  • python基于微信小程序的广西文化传承系统
  • C#采集电脑硬件(CPU、GPU、硬盘、内存等)温度和使用状况
  • 【Java高频面试问题】数据结构篇
  • 一键内网穿透,无需域名和服务器,自动https访问
  • 阿里云无影:开启云端办公娱乐新时代
  • 布瑞琳BRANEW:高端洗护领航者,铸就品质生活新典范
  • 异步IO框架io_uring实现TCP服务器
  • 程序包androidx.fragment.app不存在 import androidx.fragment.app
  • 智慧园区数字孪生最佳交付实践:沉淀可复用场景模板,实现快速部署与定制化开发