go语言中的GoMock

简介: 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的使用遵循四个基本步骤:


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


开始


让我们创建一个这样的文件夹


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框架可以在很多方面帮助我们,建立干净和轻量级的单元测试。与接口和依赖注入相结合。

相关文章
|
8月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
8月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
2月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
2月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
3月前
|
JSON 前端开发 Go
Go语言实战:创建一个简单的 HTTP 服务器
本篇是《Go语言101实战》系列之一,讲解如何使用Go构建基础HTTP服务器。涵盖Go语言并发优势、HTTP服务搭建、路由处理、日志记录及测试方法,助你掌握高性能Web服务开发核心技能。
|
3月前
|
Go
如何在Go语言的HTTP请求中设置使用代理服务器
当使用特定的代理时,在某些情况下可能需要认证信息,认证信息可以在代理URL中提供,格式通常是:
276 0
|
4月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
5月前
|
分布式计算 Go C++
初探Go语言RPC编程手法
总的来说,Go语言的RPC编程是一种强大的工具,让分布式计算变得简单如同本地计算。如果你还没有试过,不妨挑战一下这个新的编程领域,你可能会发现新的世界。
129 10
|
8月前
|
存储 Go
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
201 3
Go 语言入门指南:切片
|
8月前
|
算法 安全 Go
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
本文探讨了如何利用 Go 语言中的 Bloom Filter 算法提升公司局域网管理系统的性能。Bloom Filter 是一种高效的空间节省型数据结构,适用于快速判断元素是否存在于集合中。文中通过具体代码示例展示了如何在 Go 中实现 Bloom Filter,并应用于局域网的 IP 访问控制,显著提高系统响应速度和安全性。随着网络规模扩大和技术进步,持续优化算法和结合其他安全技术将是企业维持网络竞争力的关键。
167 2
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了