每天学习一个框架之: goMock

1 篇文章 0 订阅
1 篇文章 0 订阅

What is it?

gomock是go 语言实现mocking框架,它集成了golang的testing,但是能够传入其他Context,致使它有更丰富的功能。对于golang 的测试开发,当你遇到某些依赖很多中间依赖时,写测试就比较困难. 用 mock 对象模拟依赖项的行为,以屏蔽待测试方法对外部的依赖,会使得这个过程很简单。

之前是有Google开发repo在GitHub - golang/mock: GoMock is a mocking framework for the Go programming language.,后来Google 不管了,让Uber团队来继续管理,新的PR以及feature都在新的repo进行GitHub - uber-go/mock: GoMock is a mocking framework for the Go programming language.

  • 1)消除外部依赖:在单元测试中,我们希望集中关注待测试的单元,而不是受其影响的外部依赖。通过使用 Gomock,我们可以轻松创建模拟对象来替代真实的外部依赖,从而将被测单元与其依赖解耦。
  • 2)简化测试设置和验证:Gomock 提供了简洁的 API,可以用来设置预期行为和验证模拟对象的调用。我们可以设定模拟对象的方法应该被调用多少次、以及在调用时应该返回什么结果。然后,通过调用控制器的验证方法,我们可以确保模拟对象的调用符合预期。
  • 3)提高测试代码的可读性和可维护性:使用 Gomock,我们可以编写更简洁、更可读的测试代码。通过定义模拟对象的预期行为,测试人员可以清晰地了解待测试单元的行为和逻辑,而无需深入研究外部依赖的实现细节。

快速安装开始

安装

go install go.uber.org/mock/mockgen@latest

#检测安装完成
mockgen -version

mock使用四部曲

mock 使用很简单,大概需要以下几个步骤:

  1.  (测试需求分析)写好代码之后,发现在写测试代码时候,需要依赖中间件的基础,比如数据库连接,网络IO,文件读写等等,导致测试代码下不下去,那么就可以考虑用mock,将依赖的内容通过mock的方式实现,也就是mock的对象是那些依赖的中间件。
  2. (生成mock代码)通过mockgen命令行工具帮你生成数据库连接,网络IO,文件读写等mock_***.go代码文件。
  3. 根据需求打桩)有了mock_***.go,打几个桩子Stubs,(稍后解释),告知你的测试程序走到这里期待的输入,输出是什么,让测试程序不被阻塞
  4. 循环往复)通过“八股文”方式将所有可能的桩子都打好之后,也就是那些需要mock的中间件(比如数据库连接,文件读写等)的所有情况都被模拟考虑进去了,你就自在写你的逻辑代码的测试代码的任何阻碍都没有了,按照往常写测试代码就可以

接下来就是一步一步实战对于DB 类型相关的测试代码mock方式。

测试需求分析

下面是我的项目的文件目录格式,主要是有个db的package,mysql实现了db的其中一类数据库模型。

.
├── _output
│   └── go_build_myProject_io
├── go.mod
├── go.sum
├── main.go
├── main_test.go
└── pkg
    └── db
        ├── interface.go
        └── mysql
            ├── mysql.go
            └── mysql_test.go

现在我的主要业务逻辑main.go 想要写一个测试文件main_test.go,一下是main.go的内容, getFromDBAndHandleIt是我想测试function,但是由于中间有数据库连接,怎么办呢?

# main.go

package myGoProjects

import (
	"fmt"
	"myProject.io/pkg/db"
)

func getFromDBAndHandleIt(myDB db.DB, input string) (string, error) {
	result, err := myDB.Get(input)
	if err != nil {
		return "", err
	}

	// Start to handle the result from DB
	fmt.Println(result)

	return result, nil
}

此时就是需要mock的时候,数据库那一步就是你要mock的一个步骤。 现在我们目标很明确,就是要mock数据库连接,也就是通过mock工具来对数据库这个interface来mock.

生成mock代码

mockgen 有两种执行操作模式:source 模式;reflect模式。这里只讲第一种,而且大部分大家都是使用第一种居多。通过--source 的命令根据源代码生成mock 接口。

for example,我想mock数据库那么就要提供一下相关信息:

  1. mock对象代码
  2. mock代码输出位置
  3. mock 代码应该属于哪一个package

基于以上三点,我们要对pkg/db的数据库接口interface.go 进行mock,mock代码以db_mock.go的形式放在interface.go同目录下,以后所有其他使用数据库并且想通过mock数据库连接的测试代码都可以在db 这个package下面进行查找即可。

mockgen -source=pkg/db/interface.go -destination=pkg/db/db_mock.go -package=db

成功生成db_mock.go之后的目录文件格式如下, 多一个pkg/db/db_mock.go

.
├── _output
│   └── go_build_myProject_io
├── go.mod
├── go.sum
├── main.go
├── main_test.go
└── pkg
    └── db
        ├── db_mock.go
        ├── interface.go
        └── mysql
            ├── mysql.go
            └── mysql_test.go

根据需求打桩

pkg/db的mock我们已经mock成功了,别忘了我们初心,就是想写main_test.go 测试getFromDBAndHandleIt方法。 接下来就是想回归到main_test.go 通过打桩子,让自己的测试文件不被阻塞。

直接展示结果。以下是对包含数据库mock的测试代码:

切记,既然你要fake 一个不是实际的DB,那么你在测试代码中使用的DB就必须得是mocked DB 对象。而且这个mocked 的DB对象是按照你打好桩按照你意愿进行输入输出的mock对象。

# main_test.go


package myGoProjects

import (
	"errors"
	"testing"

	"go.uber.org/mock/gomock"
	"myProject.io/pkg/db"
)

func Test_getFromDBAndHandleIt(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish() // 断言 DB.Get() 方法是否被调用

	mockedDB := db.NewMockDB(ctrl)
	// 打桩1:DB.Get("Tom") 方法返回值为 "",错误为"not exist"
	mockedDB.EXPECT().Get(gomock.Eq("Tom")).Return("", errors.New("not exist"))
	// DB exists, 返回结果为 "Jerry",错误为 nil
	mockedDB.EXPECT().Get(gomock.Eq("Jerry")).Return("Jerry", nil)

	// 测试用例1:
	// 输入:"Tom"
	// 预期输出:"",错误:"not exist"
	if v, err := getFromDBAndHandleIt(mockedDB, "Tom"); v != "" || err == nil {
		t.Fatal("expected \"\" and not exist error, but got", v, err)
	}

	// 测试用例2:
	// 输入:"Jerry"
	// 预期输出:"Jerry result",错误:nil
	if v, err := getFromDBAndHandleIt(mockedDB, "Jerry"); v != "Jerry" || err != nil {
		t.Fatal("expected Jerry and nil error, but got", v, err)
	}

}

从上面代码可以看出,有两个桩子(我们假定的mock输入输出)

1. 输入Tom, 我们期望DB输出“” +  not exist error

2. 输入Jerry,我们期望DB输出"Jerry" + nil

所以整理抽象出来的pattern 就是:

# 创建gomock 控制器
	
ctrl := gomock.NewController(t)
defer ctrl.Finish() 


# 针对mockgen 对中间件生成的mock对象进行打桩。基本形式是:
mockedObject := pkg.NewMockedObject(ctrl)

## 打桩

mockedObject.EXPECT().Get(gomock.Eq("expected_input")).Return("expected_output",...)


# 将mockedObject 中间件代替正式的中间件来unblock 测试代码的争取进行。

// test case1
...

// test case2
...
...



循环往复

根据上面的实例,再写测试代码的时候,见招拆招。遇到什么需要被“山寨”的,就按照“四部曲”进行循环往复进行。

总结

没有总结。是不是很简单,希望你能有收获。

看到这就点个关注吧。

配套gitlink: https://github.com/mhkyle/myGoMockLesson (欢迎start + follow互粉,有机会在world最大同性交友平台github上meet)

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值