golang单元测试模拟框架插件gomock的使用

golang单元测试模拟框架插件gomock的使用

简介

gomock是Go编程语言的模拟框架。它与Go内置的testing包很好地集成,但也可以在其他上下文中使用。

支持的Go版本

go.uber.org/mock支持官方Go发布策略支持的所有Go版本,即最近的2个Go版本。

安装

安装mockgen工具:

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

验证安装是否成功:

mockgen -version

如果失败,请确保GOPATH/bin在你的PATH中:

export PATH=$PATH:$(go env GOPATH)/bin

运行mockgen

mockgen有三种操作模式:archive、source和package。

Archive模式

从包存档文件(.a)生成模拟接口:

# Build the package to a archive.
go build -o pkg.a database/sql/driver

mockgen -archive=pkg.a database/sql/driver Conn,Driver

Source模式

从源文件生成模拟接口:

mockgen -source=foo.go [other options]

Package模式

通过指定包和接口名称工作:

mockgen database/sql/driver Conn,Driver

# Convenient for `go:generate`.
mockgen . Conn,Driver

完整示例

定义接口

type Foo interface {
  Bar(x int) int
}

func SUT(f Foo) {
 // ...
}

构建Mock测试

func TestFoo(t *testing.T) {
  ctrl := gomock.NewController(t)

  m := NewMockFoo(ctrl)

  // Asserts that the first and only call to Bar() is passed 99.
  // Anything else will fail.
  m.
    EXPECT().
    Bar(gomock.Eq(99)).
    Return(101)

  SUT(m)
}

构建Stub测试

func TestFoo(t *testing.T) {
  ctrl := gomock.NewController(t)

  m := NewMockFoo(ctrl)

  // Does not make any assertions. Executes the anonymous functions and returns
  // its result when Bar is invoked with 99.
  m.
    EXPECT().
    Bar(gomock.Eq(99)).
    DoAndReturn(func(_ int) int {
      time.Sleep(1*time.Second)
      return 101
    }).
    AnyTimes()

  // Does not make any assertions. Returns 103 when Bar is invoked with 101.
  m.
    EXPECT().
    Bar(gomock.Eq(101)).
    Return(103).
    AnyTimes()

  SUT(m)
}

修改失败消息

修改Want值

gomock.WantFormatter(
  gomock.StringerFunc(func() string { return "is equal to fifteen" }),
  gomock.Eq(15),
)

修改Got值

gomock.GotFormatterAdapter(
  gomock.GotFormatterFunc(func(i any) string {
    // Leading 0s
    return fmt.Sprintf("%02d", i)
  }),
  gomock.Eq(15),
)

更多关于golang单元测试模拟框架插件gomock的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang单元测试模拟框架插件gomock的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang单元测试模拟框架gomock使用指南

什么是gomock

gomock是Go语言官方提供的mock框架,用于在单元测试中创建和管理mock对象。它通常与mockgen工具配合使用,可以基于接口自动生成mock实现。

安装gomock

首先需要安装gomock和mockgen工具:

go install github.com/golang/mock/mockgen@latest
go get github.com/golang/mock/gomock

基本使用流程

1. 定义接口

假设我们有一个简单的接口:

// user.go
package user

type UserRepository interface {
    GetUser(id int) (*User, error)
    SaveUser(user *User) error
}

type User struct {
    ID   int
    Name string
}

2. 生成mock代码

使用mockgen生成mock实现:

mockgen -source=user.go -destination=mock_user.go -package=user

这会生成一个mock_user.go文件,包含MockUserRepository结构体。

3. 编写测试用例

// user_test.go
package user

import (
    "testing"
    
    "github.com/golang/mock/gomock"
    "github.com/stretchr/testify/assert"
)

func TestGetUser(t *testing.T) {
    // 创建gomock控制器
    ctrl := gomock.NewController(t)
    defer ctrl.Finish() // 断言mock方法被调用
    
    // 创建mock对象
    mockRepo := NewMockUserRepository(ctrl)
    
    // 设置预期行为
    expectedUser := &User{ID: 1, Name: "John"}
    mockRepo.EXPECT().
        GetUser(1). // 预期调用参数
        Return(expectedUser, nil). // 预期返回值
        Times(1) // 预期调用次数
    
    // 注入mock对象并测试
    service := UserService{Repo: mockRepo}
    user, err := service.GetUser(1)
    
    // 验证结果
    assert.NoError(t, err)
    assert.Equal(t, expectedUser, user)
}

gomock常用功能

1. 参数匹配

gomock提供了多种参数匹配方式:

// 精确匹配
mockRepo.EXPECT().GetUser(1).Return(user, nil)

// 任意参数
mockRepo.EXPECT().GetUser(gomock.Any()).Return(user, nil)

// 自定义匹配器
mockRepo.EXPECT().GetUser(gomock.Eq(1)).Return(user, nil)

2. 调用次数控制

// 精确调用次数
mockRepo.EXPECT().GetUser(1).Return(user, nil).Times(2)

// 至少调用n次
mockRepo.EXPECT().GetUser(1).Return(user, nil).MinTimes(1)

// 最多调用n次
mockRepo.EXPECT().GetUser(1).Return(user, nil).MaxTimes(3)

// 任意次数(0或多次)
mockRepo.EXPECT().GetUser(1).Return(user, nil).AnyTimes()

3. 调用顺序控制

gomock.InOrder(
    mockRepo.EXPECT().GetUser(1).Return(user1, nil),
    mockRepo.EXPECT().GetUser(2).Return(user2, nil),
)

4. 动态返回值

mockRepo.EXPECT().GetUser(gomock.Any()).
    DoAndReturn(func(id int) (*User, error) {
        return &User{ID: id, Name: "Dynamic"}, nil
    })

5. 副作用操作

mockRepo.EXPECT().SaveUser(gomock.Any()).
    Do(func(user *User) {
        t.Log("User saved:", user)
    }).
    Return(nil)

最佳实践

  1. 每个测试用例只测试一个功能点:保持测试简单明确
  2. 合理设置预期:不要过度mock,只mock必要的依赖
  3. 验证调用次数:确保依赖被正确调用
  4. 清理资源:使用defer ctrl.Finish()确保mock验证被执行
  5. 结合表格驱动测试:对于多组输入输出的测试更清晰
func TestGetUser(t *testing.T) {
    tests := []struct {
        name     string
        userID   int
        mockUser *User
        mockErr  error
        wantErr  bool
    }{
        {"success", 1, &User{ID: 1, Name: "John"}, nil, false},
        {"not found", 2, nil, errors.New("not found"), true},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            ctrl := gomock.NewController(t)
            defer ctrl.Finish()
            
            mockRepo := NewMockUserRepository(ctrl)
            mockRepo.EXPECT().
                GetUser(tt.userID).
                Return(tt.mockUser, tt.mockErr)
            
            service := UserService{Repo: mockRepo}
            user, err := service.GetUser(tt.userID)
            
            if tt.wantErr {
                assert.Error(t, err)
            } else {
                assert.NoError(t, err)
                assert.Equal(t, tt.mockUser, user)
            }
        })
    }
}

gomock是Go单元测试中非常强大的工具,合理使用可以大大提高测试的可靠性和可维护性。

回到顶部