本篇内容介绍了“Golang如何实现单元测试中的逻辑层”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
准备工作
安装
go install github.com/golang/mock/mockgen@v1.6.0
基本 case 代码
首先我们还是基于上一次的例子,这里给出上一次例子中所用到的接口
package service
import (
"context"
"fmt"
"go-demo/m/unit-test/entity"
)
type UserRepo interface {
AddUser(ctx context.Context, user *entity.User) (err error)
DelUser(ctx context.Context, userID int) (err error)
GetUser(ctx context.Context, userID int) (user *entity.User, exist bool, err error)
}
type UserService struct {
userRepo UserRepo
}
func NewUserService(userRepo UserRepo) *UserService {
return &UserService{userRepo: userRepo}
}
func (us *UserService) AddUser(ctx context.Context, username string) (err error) {
if len(username) == 0 {
return fmt.Errorf("username not specified")
}
return us.userRepo.AddUser(ctx, &entity.User{Username: username})
}
func (us *UserService) GetUser(ctx context.Context, userID int) (user *entity.User, err error) {
userInfo, exist, err := us.userRepo.GetUser(ctx, userID)
if err != nil {
return nil, err
}
if !exist {
return nil, fmt.Errorf("user %d not found", userID)
}
return userInfo, nil
}
可以看到我们的目标很明确,就是需要 mock 掉
UserRepo
接口的几个方法,就可以测试我们 AddUser
和 GetUser
方法了生成 mock 接口
使用
mockgen
命令可以生成我们所需要的 mock 接口mockgen -source=./service/user.go -destination=./mock/user_mock.go -package=mock
参数名称都很好理解,我这边不赘述了。命令执行完成之后,会在
destination
生成对于的 mock 接口,就可以使用了。生成的代码大致如下面的样子,可以简单瞄一眼:
// Code generated by MockGen. DO NOT EDIT.
// Source: ./user.go
// Package mock is a generated GoMock package.
package mock
import (
context "context"
entity "go-demo/m/unit-test/entity"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockUserRepo is a mock of UserRepo interface.
type MockUserRepo struct {
ctrl *gomock.Controller
recorder *MockUserRepoMockRecorder
}
// MockUserRepoMockRecorder is the mock recorder for MockUserRepo.
type MockUserRepoMockRecorder struct {
mock *MockUserRepo
}
// NewMockUserRepo creates a new mock instance.
func NewMockUserRepo(ctrl *gomock.Controller) *MockUserRepo {
mock := &MockUserRepo{ctrl: ctrl}
mock.recorder = &MockUserRepoMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockUserRepo) EXPECT() *MockUserRepoMockRecorder {
return m.recorder
}
// AddUser mocks base method.
func (m *MockUserRepo) AddUser(ctx context.Context, user *entity.User) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddUser", ctx, user)
ret0, _ := ret[0].(error)
return ret0
}
// AddUser indicates an expected call of AddUser.
func (mr *MockUserRepoMockRecorder) AddUser(ctx, user interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUser", reflect.TypeOf((*MockUserRepo)(nil).AddUser), ctx, user)
}
// DelUser mocks base method.
func (m *MockUserRepo) DelUser(ctx context.Context, userID int) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DelUser", ctx, userID)
ret0, _ := ret[0].(error)
return ret0
}
// DelUser indicates an expected call of DelUser.
func (mr *MockUserRepoMockRecorder) DelUser(ctx, userID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DelUser", reflect.TypeOf((*MockUserRepo)(nil).DelUser), ctx, userID)
}
// GetUser mocks base method.
func (m *MockUserRepo) GetUser(ctx context.Context, userID int) (*entity.User, bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUser", ctx, userID)
ret0, _ := ret[0].(*entity.User)
ret1, _ := ret[1].(bool)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// GetUser indicates an expected call of GetUser.
func (mr *MockUserRepoMockRecorder) GetUser(ctx, userID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockUserRepo)(nil).GetUser), ctx, userID)
}
编写单元测试
gomock
的单元测试编写起来也很方便,只需要调用 EXPECT()
方法,将需要 mock 的接口对应需要的返回值就可以了。我们直接来看例子:package service
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"go-demo/m/unit-test/entity"
"go-demo/m/unit-test/mock"
)
func TestUserService_AddUser(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
mockUserRepo := mock.NewMockUserRepo(ctl)
userInfo := &entity.User{Username: "LinkinStar"}
// 无论对 AddUser 方法输入任意参数,均会返回 userInfo 信息
mockUserRepo.EXPECT().AddUser(gomock.Any(), gomock.Any()).Return(nil)
userService := NewUserService(mockUserRepo)
err := userService.AddUser(context.TODO(), userInfo.Username)
assert.NoError(t, err)
}
func TestUserService_GetUser(t *testing.T) {
ctl := gomock.NewController(t)
defer ctl.Finish()
userID := 1
username := "LinkinStar"
mockUserRepo := mock.NewMockUserRepo(ctl)
// 只有当对于 GetUser 传入 userID 为 1 时才会返回 user 信息
mockUserRepo.EXPECT().GetUser(context.TODO(), userID).Return(&entity.User{
ID: userID,
Username: username,
}, true, nil)
userService := NewUserService(mockUserRepo)
userInfo, err := userService.GetUser(context.TODO(), userID)
assert.NoError(t, err)
assert.Equal(t, username, userInfo.Username)
}
与之前一样,我们依旧使用
github.com/stretchr/testify
做断言来验证最终结果。可以看到,单元测试编写起来并不难。优化
当然,如果我们每次修改接口或者新增接口都需要重新执行一次命令,一个文件还好,当有很多文件的时候肯定是非常困难的。所以我们需要使用 go:generate 来优化一下。
我们可以在需要 mock 的接口上方加入注释(注意这里写的路径要和实际路径相符合):
//go:generate mockgen -source=./user.go -destination=../mock/user_mock.go -package=mock
type UserRepo interface {
AddUser(ctx context.Context, user *entity.User) (err error)
DelUser(ctx context.Context, userID int) (err error)
GetUser(ctx context.Context, userID int) (user *entity.User, exist bool, err error)
}
然后只需要使用命令
go generate ./...
就可以生成全部的 mock 嘞,所以及时文件很多,只需要利用好 go:generate 也能一次搞定。
版权声明:除特别声明外,本站所有文章皆是本站原创,转载请以超链接形式注明出处!