使用 gomock 测试 Go 代码

gomock 是 Google 推出的用于 Go 的 mock 工具。它的大致用法是:

  • 需要 mock 的地方,使用接口
  • 执行 mockgen 生成代码
  • 导入生成的代码,并且开始设置 mock 函数的行为
  • 开始测试

安装

我们首先来看一下如何安装,如果 Go 的版本比较老,小于 1.16,那么就执行:

$ GO111MODULE=on go get github.com/golang/mock/[email protected]
$

如果大于,就执行:

$ go install github.com/golang/mock/[email protected]
$

使用

mockgen 有两种使用方式,第一种是 mockgen -source=foo.go,指明从哪个源文件生成,第二种是 mockgen database/sql/driver Conn,Driver,指明在哪个包的哪些接口。

此外,除了直接使用命令行,还可以写在 Makefile 里,或者是在 Go 源码里,以 go:generate 的方式写然后执行 go generate

案例

首先,我们来一个demo,加上 go:generate 来生成,用第一种方式:

package main

import (
	"fmt"
)

//go:generate mockgen -source=./main.go -destination mock_main.go  -package main

type Foo interface {
	SayHi(sth string) error
}

type foo struct{}

func (f *foo) SayHi(sth string) error {
	fmt.Printf("sth: %s\n", sth)
	return nil
}

func main() {
	f := foo{}
	f.SayHi("hi foo")
}

生成代码:

$ go generate
$ ls
main.go  mock_main.go  trygomock

mockgen 还有其它参数,具体需要阅读一下 --help

$ mockgen --help
mockgen has two modes of operation: source and reflect.
...

接下来我们就可以使用 gomock 生成的方法来进行测试了,下列内容保存为 main_test.go

$ cat -n main_test.go 
     1	package main
     2	
     3	import (
     4		"testing"
     5	
     6		"github.com/golang/mock/gomock"
     7	)
     8	
     9	func TestFoo(t *testing.T) {
    10		ctrl := gomock.NewController(t) // 初始化 controller
    11		defer ctrl.Finish()
    12	
    13		mockFoo := NewMockFoo(ctrl) // 初始化 mock
    14	
    15		mockFoo.EXPECT().SayHi(gomock.Any()).Return(nil) // 设置期望的入参和出参
    16		if err := mockFoo.SayHi("haha"); err != nil {    // 检查
    17			t.Fatalf("bad return value: %s", err)
    18		}
    19	
    20		mockFoo.EXPECT().SayHi("nonono").Return(nil) // 同理,可以自动检查入参是否匹配
    21		if err := mockFoo.SayHi("nonono"); err != nil {
    22			t.Fatalf("bad return value: %s", err)
    23		}
    24	
    25		mockFoo.EXPECT().SayHi("nonono").Return(nil) // 这里不匹配,就会失败
    26		if err := mockFoo.SayHi("haha"); err == nil {
    27			t.Fatalf("bad return value: %s", err)
    28		}
    29	}

执行一下:

$ go test ./...
--- FAIL: TestFoo (0.00s)
    main_test.go:26: Unexpected call to *main.MockFoo.SayHi([haha]) at /home/jiajun/Code/test/trygomock/main_test.go:26 because: 
        expected call at /home/jiajun/Code/test/trygomock/main_test.go:25 doesn't match the argument at index 0.
        Got: haha (string)
        Want: is equal to nonono (string)
        expected call at /home/jiajun/Code/test/trygomock/main_test.go:15 has already been called the max number of times
        expected call at /home/jiajun/Code/test/trygomock/main_test.go:20 doesn't match the argument at index 0.
        Got: haha (string)
        Want: is equal to nonono (string)
    controller.go:269: missing call(s) to *main.MockFoo.SayHi(is equal to nonono (string)) /home/jiajun/Code/test/trygomock/main_test.go:25
    controller.go:269: aborting test due to missing call(s)
FAIL
FAIL	github.com/jiajunhuang/test/trygomock	0.001s
FAIL

可以看到,在26行调用的时候失败了。这就是一个 gomock 使用的简单例子。

gomock 使用方法概览

这一节,我们回顾一下使用方法:

  • 首先要把需要 mock 的地方,写成接口
  • 然后执行 mockgen 生成代码
  • 在单元测试中,首先执行 ctrl := gomock.NewController(t) 然后 defer ctrl.Finish()
  • 使用 NewMockXXX 生成 mock 对象
  • 调用 EXPECT() 方法,开始设置断言,其中参数,如果输入具体的类型,那么执行时就要传入具体的类型,如果输入 gomock.Any() 的话,就会忽略参数类型,还有 gomock.Eq, gomock.Len, gomock.All
  • 可以通过 Return 设置返回结果
  • 可以通过 Times 设置调用次数,还有 AnyTimes 不限次数,MaxTimes 最多执行次数,MinTimes 最少执行次数
  • 可以通过 After 设置执行顺序

总结

通过这篇文章我们学习了 gomock 的使用方法,gomock 在单元测试中,控制一些接口的表现以及返回结果都是非常有用的,希望 这篇文章能够对读者产生帮助。


更多文章
  • 利用Github的WebHook完成自动部署
  • 使用Tornado和rst来写博客
  • Haskell do notation
  • foldl 和 foldr 的变换
  • Haskell TypeClass 笔记
  • 重新捡起你那吃灰的树莓派
  • Tornado 源码阅读
  • JavaScript权威指南笔记
  • Python零碎知识汇总
  • C语言的位操作
  • 分治
  • 关于python的decorator和descriptor
  • 程序设计实践笔记
  • Thinking Recursively
  • Block I/O