go语言中GoMock安装使用详解

目录
  • 安装
  • 开始
  • 使用参数匹配器
  • 经验之谈

安装

GoMock是一个Go框架。它与内置的测试包整合得很好,并在单元测试时提供了灵活性。正如我们所知,对具有外部资源(数据库、网络和文件)或依赖关系的代码进行单元测试总是很麻烦。

为了使用GoMock,我们需要安装gomockgithub.com/golang/mock/gomockmockgen代码生成工具github.com/golang/mock/mockgen。使用这个命令行:

go get github.com/golang/mock/gomock
go get github.com/golang/mock/mockgen

GoMock的使用遵循四个基本步骤:

  • 使用mockgen为你想模拟的接口生成一个模拟对象。
  • 在测试部分,创建一个gomock.Controller的实例,并把它传递给你的mock对象的构造函数以获得一个mock对象。
  • 在mock上调用EXPECT()来设置期望值和返回值。
  • 在模拟控制器上调用Finish()来断言模拟对象的期望。

开始

让我们创建一个这样的文件夹(本代码在 go1.16.15 版本下执行)

gomock_test
├── doer
│ └── doer.go
├── mocks
│ └── mock_doer.go
└── user
  ├── user.go
  └── user_test.go

doer/doer.go

package doer
type Doer interface {
  DoSomething(int, string) error
  SaySomething(string) string
}

那么这里是我们在模拟Doer接口时要测试的代码。

user/user.go

package user
import "gomock_test/doer"
const (
	filtered   = "OK"
	unfiltered = "spam"
	Nice       = "nice"
	Bad        = "bad"
)
type User struct {
	// struct while mocking the doer interface
	Doer doer.Doer
}
// method Use using it
func (u *User) Use() error {
	return u.Doer.DoSomething(123, "Hello GoMock")
}
func (u *User) SaySomething(num int) string {
	if num == 3 {
		return u.Doer.SaySomething(unfiltered)
	}
	return u.Doer.SaySomething(filtered)
}
type Student struct{}
func (s *Student) DoSomething(_ int, _ string) error {
	panic("not implemented") // TODO: Implement
}
func (s *Student) SaySomething(kata string) string {
	if kata == filtered {
		return Nice
	}
	return Bad
}

我们将把Doer的模拟放在一个包mocks中。我们首先创建一个包含我们的模拟实现的目录mocks,然后在doer包上运行mockgen

mockgen -destination=../mocks/mock_doer.go -package=mocks gomock_test/doer Doer

NOTE: 在执行这步的时候,会报错:Failed to format generated source code: mocks/mock_doer.go:5:15: expected ';', found '.’ 这个时候,我们只需要将打印出来的代码复制到我们对应的文件中即可。

当有大量的接口/包需要模拟时,为每个包和接口运行mockgen是一种乌托邦。为了缓解这个问题,可以将mockgen命令与go:generate放在一起。

go:generate mockgen -destination=../mocks/mock_doer.go -package=mocks gomock_test/doer Doer

我们必须自己创建目录模拟,因为GoMock不会为我们这样做,而是会以错误退出。

  • destination=.../mocks/mock_doer.go : 把生成的mocks放在这个路径下。
  • -package=mocks : 把生成的mocks放在这个包里
  • gomock_test/doer : 为这个包生成mocks。
  • Doer : 为这个接口生成mocks(必填),因为我们需要指定哪个接口来生成mocks。(如果需要的话,可以用逗号分隔的列表来指定多个接口。例如,Doer1, Doer2)

因为我们对mockgen的调用在我们的项目中放置了一个文件mocks/mock_doer.go。这就是这样一个生成的mock实现的样子:

// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/timliudream/go-test/gomock_test/doer (interfaces: Doer)
// Package github.com/timliudream/go-test/gomock_test/mocks is a generated GoMock package.
package mocks
import (
	gomock "github.com/golang/mock/gomock"
	reflect "reflect"
)
// MockDoer is a mock of Doer interface.
type MockDoer struct {
	ctrl     *gomock.Controller
	recorder *MockDoerMockRecorder
}
// MockDoerMockRecorder is the mock recorder for MockDoer.
type MockDoerMockRecorder struct {
	mock *MockDoer
}
// NewMockDoer creates a new mock instance.
func NewMockDoer(ctrl *gomock.Controller) *MockDoer {
	mock := &MockDoer{ctrl: ctrl}
	mock.recorder = &MockDoerMockRecorder{mock}
	return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockDoer) EXPECT() *MockDoerMockRecorder {
	return m.recorder
}
// DoSomething mocks base method.
func (m *MockDoer) DoSomething(arg0 int, arg1 string) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "DoSomething", arg0, arg1)
	ret0, _ := ret[0].(error)
	return ret0
}
// DoSomething indicates an expected call of DoSomething.
func (mr *MockDoerMockRecorder) DoSomething(arg0, arg1 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DoSomething", reflect.TypeOf((*MockDoer)(nil).DoSomething), arg0, arg1)
}
// SaySomething mocks base method.
func (m *MockDoer) SaySomething(arg0 string) string {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "SaySomething", arg0)
	ret0, _ := ret[0].(string)
	return ret0
}
// SaySomething indicates an expected call of SaySomething.
func (mr *MockDoerMockRecorder) SaySomething(arg0 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaySomething", reflect.TypeOf((*MockDoer)(nil).SaySomething), arg0)
}

接下来,我们在测试中定义一个模拟控制器。一个模拟控制器负责跟踪和断言其相关模拟对象的期望。我们可以通过传递一个*testing.T类型的值给它的构造函数来获得一个模拟控制器,并使用它来构造一个Doer接口的模拟对象。

//core of gomock
mockCtrl := gomock.NewController(t)
//used to trigger final assertions. if its ignored, mocking assertions will never fail
defer mockCtrl.Finish()
// create a new mock object, passing the controller instance as parameter
// for a newly created mock object it will accept any input and outpuite
// need to define its behavior with the method expect
mockDoer := mocks.NewMockDoer(mockCtrl)

使用参数匹配器

在GoMock中,一个参数可以被期望有一个固定的值,也可以被期望与一个谓词(称为匹配器)相匹配。匹配器用于表示被模拟方法的预期参数范围。下列匹配器在Gomock中被预先定义了:

  • gomock.Any() : 匹配任何值(任何类型)。
  • gomock.Eq(x) : 使用反射来匹配与x深度相等的值。
  • gomock.Nil() : 匹配nil

user/user_test.go

package user
import (
	"fmt"
	"github.com/golang/mock/gomock"
	"gomock_test/mocks"
	"testing"
)
func TestUse(t *testing.T) {
	//core of gomock
	mockCtrl := gomock.NewController(t)
	//used to trigger final assertions. if its ignored, mocking assertions will never fail
	defer mockCtrl.Finish()
	// create a new mock object, passing the controller instance as parameter
	// for a newly created mock object it will accept any input and outpuite
	// need to define its behavior with the method expect
	mockDoer := mocks.NewMockDoer(mockCtrl)
	testUser := &User{Doer: mockDoer}
	//
	// Expect Do to be called once with 123 and "Hello GoMock" as parameters, and return nil from the mocked call.
	mockDoer.EXPECT().DoSomething(123, "Hello GoMock").Return(nil).Times(1)
	fmt.Println(testUser.Use())
	fmt.Println()
}
func TestUser_SaySomething(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()
	mockDoer := mocks.NewMockDoer(mockCtrl)
	user := User{
		Doer: mockDoer,
	}
	type args struct {
		num int
	}
	tests := []struct {
		name    string
		args    args
		want    string
		expect  func()
		wantErr bool
	}{
		{
			name: "Positive test case 1",
			expect: func() {
				mockDoer.EXPECT().SaySomething("spam").Return("bad")
			},
			args:    args{num: 3},
			wantErr: false,
			want:    "bad",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.expect()
			if got := user.SaySomething(tt.args.num); (got != tt.want) != tt.wantErr {
				fmt.Println("gott :", got)
				t.Errorf("User.SaySomething() = %v, want %v", got, tt.want)
			}
		})
	}
}

而单元测试的结果将是这样的:

=== RUN   TestUser_SaySomething
=== RUN   TestUser_SaySomething/Positive_test_case_1
--- PASS: TestUser_SaySomething (0.00s)
    --- PASS: TestUser_SaySomething/Positive_test_case_1 (0.00s)
PASS
ok      github.com/tokopedia/go_learning/udemy/pzn/gomock_test/user     1.100s

经验之谈

可以在不调用外部依赖的情况下进行单元测试。使用mocking框架可以在很多方面帮助我们,建立干净和轻量级的单元测试。与接口和依赖注入相结合。

以上就是go语言中GoMock安装使用详解的详细内容,更多关于go语言GoMock安装的资料请关注我们其它相关文章!

(0)

相关推荐

  • Go单元测试对GORM进行Mock测试

    目录 前言 项目准备 初始化测试工作 对Create进行Mock测试 Get 操作的Mock测试 Update 操作的Mock测试 总结 前言 在 Go 单元测试这个系列的第二部分 数据库的Mock测试 中我们介绍了用 go-sqlmock 给数据库的 CRUD 操作做Mock 测试的方法,不过里面只是讲解了一下怎么对原生的database/sql执行的 SQL 进行 Mock 测试. 真实的开发场景下我们的项目一般都会使用 ORM ,而不是原生的database/sql来完成数据库操作.在很多

  • 使用 gomonkey Mock 函数及方法示例详解

    目录 前言 函数 方法 参考 前言 在 Golang 语言中,写单元测试的时候,不可避免的会涉及到对其他函数及方法的 Mock,即在假设其他函数及方法响应预期结果的同时,校验被测函数的响应是否符合预期. 其中,在 Mock 其他函数及方法的时候,我们常用到的一个测试类库是「gomonkey」.特别地,对于方法和函数的 Mock,略有差异,在这里我们就分别给出函数和方法 Mock 示例,方便大家参考. 函数 在 Golang 语言中,函数是没有接受者的方法,其形式为 func function_n

  • go mock server的简易实现示例

    目录 前言 代码 步骤1 步骤2 步骤3 步骤4 最终效果 最后 前言 学习golang也一段时间了,看了一些书,上周又看了一本入门级的<Go语言趣学指南>,是时候检验成果了. 目的:通过读取本地mock数据,发起http请求,返回给前端,实现mock功能. 代码 整体代码只在50行左右,是一个极其简陋的mock server,没有多余的功能(主要是不会 步骤1 扫描本地以json结尾的文件,获取文件路径 // 获取当前json文件所在的路径已经文件名,然后进行拼接 func ParsePat

  • Go语言Mock使用基本指南详解

    当前的实践中问题 在项目之间依赖的时候我们往往可以通过mock一个接口的实现,以一种比较简洁.独立的方式,来进行测试.但是在mock使用的过程中,因为大家的风格不统一,而且很多使用minimal implement的方式来进行mock,这就导致了通过mock出的实现各个函数的返回值往往是静态的,就无法让caller根据返回值进行的一些复杂逻辑. 首先来举一个例子 package task type Task interface { Do(int) (string, error) } 通过mini

  • 使用Gomock进行单元测试的方法示例

    在开发过程中往往需要配合单元测试,但是很多时候,单元测试需要依赖一些比较复杂的准备工作,比如需要依赖数据库环境,需要依赖网络环境,单元测试就变成了一件非常麻烦的事情.举例来说,比如我们需要请求一个网页,并将请求回来的数据进行处理.在刚开始的时候,我通常都会先启动一个简单的http服务,然后再运行我的单元测试.可是这个单元测试测起来似乎非常笨重.甚至在持续集成过程中,我还为了能够自动化测试,特意写了一个脚本自动启动相应的服务.事情似乎需要进行一些改变. mock对象就是为了解决上面的问题而诞生的,

  • 用gomock进行mock测试的方法示例

    在开发过程中往往需要配合单元测试,但是很多时候,单元测试需要依赖一些比较复杂的准备工作,比如需要依赖数据库环境,需要依赖网络环境,单元测试就变成了一件非常麻烦的事情.举例来说,比如我们需要请求一个网页,并将请求回来的数据进行处理.在刚开始的时候,我通常都会先启动一个简单的http服务,然后再运行我的单元测试.可是这个单元测试测起来似乎非常笨重.甚至在持续集成过程中,我还为了能够自动化测试,特意写了一个脚本自动启动相应的服务.事情似乎需要进行一些改变. mock对象就是为了解决上面的问题而诞生的,

  • Go单元测试对数据库CRUD进行Mock测试

    目录 go-sqlmock 安装 使用示例 miniredis 安装 使用示例 总结 最近在实践中也总结了一些如何用表格驱动的方式使用 gock Mock测试外部接口调用.以及怎么对GORM做mock测试,这些等这篇学完基础后,后面再单独写文章给大家介绍. 这是Go语言单元测试系列教程的第3篇,介绍了如何使用go-sqlmock和miniredis工具进行MySQL和Redis的mock测试. 在上一篇<Go单元测试--模拟服务请求和接口返回>中,我们介绍了如何使用httptest和gock工

  • go语言中GoMock安装使用详解

    目录 安装 开始 使用参数匹配器 经验之谈 安装 GoMock是一个Go框架.它与内置的测试包整合得很好,并在单元测试时提供了灵活性.正如我们所知,对具有外部资源(数据库.网络和文件)或依赖关系的代码进行单元测试总是很麻烦. 为了使用GoMock,我们需要安装gomock包github.com/golang/mock/gomock和mockgen代码生成工具github.com/golang/mock/mockgen.使用这个命令行: go get github.com/golang/mock/

  • C 语言中strstr函数实例详解

    C 语言中strstr函数实例详解 strstr函数 strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串.如果是,则该函数返回str2在str1中首次出现的地址:否则,返回NULL const char* strstr(const char* str1,const char* str2); char* strstr(char* str1,const char* str2); 库中实现的strstr #include <stdio.h> #include <

  • Go语言中sync.Cond使用详解

    目录 sync.Cond 可以用来干什么? 与 Sync.Mutex 的区别 sync.Cond 使用场景 sync.Cond sync.Cond 有哪些方法 NewCond 创建实例 Broadcast 广播唤醒所有 Signal 唤醒一个协程 Wait 等待 代码示例 sync.Cond 可以用来干什么? Golang 的 sync 包中的 Cond 实现了一种条件变量,可以使用多个 Reader 等待公共资源. 每个 Cond 都会关联一个 Lock ,当修改条件或者调用 Wait 方法,

  • C语言中的getchar()使用详解

    目录 前言 getchar困惑的点 缓冲区 缓冲区带来的问题 getchar工作原理 解决缓冲区带来的问题之清空缓存区 解决最初的困惑 总结 前言 近期我在重新学习C语言时候,我发现了一个严重的问题,getchar我居然不会用了....也不是说不会用,我发现了一个非常让我困惑想不明白的问题.可能我在第一次接触C语言时候,就没有把这个概念弄清楚吧,以至于现在会不明白. getchar困惑的点 我利用getchar函数输入了一串字符ABCD,然后把这串字符给到ch,然后紧接着我打印这个ch,然后我得

  • go语言中decimal的用法详解

    目录 1. 精度丢失的case 2. decimal的应用场景 3. 使用decimal 4. decimal其他实用的场景 4.1 获取结果的整数部分 4.2 小数点后填充 4.3 比较数字的大小 5 小结 decimal是为了解决Golang中浮点数计算时精度丢失问题而生的一个库,使用decimal库我们可以避免在go中使用浮点数出现精度丢失的问题. github地址:https://github.com/shopspring/decimal 1. 精度丢失的case func TestFl

  • C语言中continue的用法详解

    目录 前言 continue 在while中的用法 continue 在for中的用法 continue 在剔除多余元素的用法 附:continue与break的区别 总结 前言 continue语句的作用是跳过本次循环体中余下尚未执行的语句,立即进行下一次的循环条件判定,可以理解为仅结束本次循环. 注意:continue语句并没有使整个循环终止. continue 在while中的用法 //continue 在while中的用法 #include<stdio.h> int main() {

  • 关于C语言中E-R图的详解

    E-R  英文缩写为(Entity Relationship Diagram)也称实体-联系图. 提供了表示实体类型.属性和联系的方法,用来描述现实世界的概念模型. 下面就讲详解e-r图,如下: 从上面的的图可以看到一个完整的e-r图有四个部分: 1.矩形框,矩形表示实体型,矩形框内写明实体名: 2.椭圆框,椭圆表示实体的属性,并用无向边将其与相应的实体型连接起来: 3.菱形框,菱形表示实体型之间的联系,在菱形框内写明联系名, 4.联系线,实体与属性之间:实体与联系之间:联系与属性之间用直线相连

  • R语言中devtools的使用详解

    今天安装r语言devtools包,尝试很多种方法也不能决解,下面这个问题是改变镜像,然后就会可以安装了(3.4.2版本) Warning in install.packages : InternetOpenUrl failed: '无法解析服务器的名称或地址' Warning in install.packages : InternetOpenUrl failed: '不能连接到吊销服务器,或者未能获得最终响应.' Warning in install.packages : InternetOp

  • C语言中的malloc使用详解

    一.原型:extern void *malloc(unsigned int num_bytes); 头文件:#include <malloc.h> 或 #include <alloc.h> (注意:alloc.h 与 malloc.h 的内容是完全一致的.) 功能:分配长度为num_bytes字节的内存块 说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL. 当内存不再使用时,应使用free()函数将内存块释放. 举例: #include<stdio.h&g

  • MySql 5.7.14 解压版安装步骤详解

    下面主要分为五大步给大家介绍mySql 5.7.14 解压版安装教程.感兴趣的朋友一起看看吧. 第一步:下载最近的MySQL文件并且解压: 下载最新版的MySQL–mysql-5.7.12下载地址 将下载到的文件解压缩到自己喜欢的位置,例如我自己的位置是D:\MySQL\mysql-5.7.12-winx64 第二步:配置环境变量 这里不多说,bin目录配置到path下面就行了. 第三步:添加配置文件 直接复制一个解压路径下面的 my-default.ini文件,重命名为my.ini然后编辑该文

随机推荐