Golang如何实现单元测试中的逻辑层

其他教程   发布日期:2023年08月14日   浏览次数:543

本篇内容介绍了“Golang如何实现单元测试中的逻辑层”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

准备工作

安装

  1. go install github.com/golang/mock/mockgen@v1.6.0

基本 case 代码

首先我们还是基于上一次的例子,这里给出上一次例子中所用到的接口

  1. package service
  2. import (
  3. "context"
  4. "fmt"
  5. "go-demo/m/unit-test/entity"
  6. )
  7. type UserRepo interface {
  8. AddUser(ctx context.Context, user *entity.User) (err error)
  9. DelUser(ctx context.Context, userID int) (err error)
  10. GetUser(ctx context.Context, userID int) (user *entity.User, exist bool, err error)
  11. }
  12. type UserService struct {
  13. userRepo UserRepo
  14. }
  15. func NewUserService(userRepo UserRepo) *UserService {
  16. return &UserService{userRepo: userRepo}
  17. }
  18. func (us *UserService) AddUser(ctx context.Context, username string) (err error) {
  19. if len(username) == 0 {
  20. return fmt.Errorf("username not specified")
  21. }
  22. return us.userRepo.AddUser(ctx, &entity.User{Username: username})
  23. }
  24. func (us *UserService) GetUser(ctx context.Context, userID int) (user *entity.User, err error) {
  25. userInfo, exist, err := us.userRepo.GetUser(ctx, userID)
  26. if err != nil {
  27. return nil, err
  28. }
  29. if !exist {
  30. return nil, fmt.Errorf("user %d not found", userID)
  31. }
  32. return userInfo, nil
  33. }

可以看到我们的目标很明确,就是需要 mock 掉

  1. UserRepo
接口的几个方法,就可以测试我们
  1. AddUser
  1. GetUser
方法了

生成 mock 接口

使用

  1. mockgen
命令可以生成我们所需要的 mock 接口
  1. mockgen -source=./service/user.go -destination=./mock/user_mock.go -package=mock

参数名称都很好理解,我这边不赘述了。命令执行完成之后,会在

  1. destination
生成对于的 mock 接口,就可以使用了。

生成的代码大致如下面的样子,可以简单瞄一眼:

  1. // Code generated by MockGen. DO NOT EDIT.
  2. // Source: ./user.go
  3. // Package mock is a generated GoMock package.
  4. package mock
  5. import (
  6. context "context"
  7. entity "go-demo/m/unit-test/entity"
  8. reflect "reflect"
  9. gomock "github.com/golang/mock/gomock"
  10. )
  11. // MockUserRepo is a mock of UserRepo interface.
  12. type MockUserRepo struct {
  13. ctrl *gomock.Controller
  14. recorder *MockUserRepoMockRecorder
  15. }
  16. // MockUserRepoMockRecorder is the mock recorder for MockUserRepo.
  17. type MockUserRepoMockRecorder struct {
  18. mock *MockUserRepo
  19. }
  20. // NewMockUserRepo creates a new mock instance.
  21. func NewMockUserRepo(ctrl *gomock.Controller) *MockUserRepo {
  22. mock := &MockUserRepo{ctrl: ctrl}
  23. mock.recorder = &MockUserRepoMockRecorder{mock}
  24. return mock
  25. }
  26. // EXPECT returns an object that allows the caller to indicate expected use.
  27. func (m *MockUserRepo) EXPECT() *MockUserRepoMockRecorder {
  28. return m.recorder
  29. }
  30. // AddUser mocks base method.
  31. func (m *MockUserRepo) AddUser(ctx context.Context, user *entity.User) error {
  32. m.ctrl.T.Helper()
  33. ret := m.ctrl.Call(m, "AddUser", ctx, user)
  34. ret0, _ := ret[0].(error)
  35. return ret0
  36. }
  37. // AddUser indicates an expected call of AddUser.
  38. func (mr *MockUserRepoMockRecorder) AddUser(ctx, user interface{}) *gomock.Call {
  39. mr.mock.ctrl.T.Helper()
  40. return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUser", reflect.TypeOf((*MockUserRepo)(nil).AddUser), ctx, user)
  41. }
  42. // DelUser mocks base method.
  43. func (m *MockUserRepo) DelUser(ctx context.Context, userID int) error {
  44. m.ctrl.T.Helper()
  45. ret := m.ctrl.Call(m, "DelUser", ctx, userID)
  46. ret0, _ := ret[0].(error)
  47. return ret0
  48. }
  49. // DelUser indicates an expected call of DelUser.
  50. func (mr *MockUserRepoMockRecorder) DelUser(ctx, userID interface{}) *gomock.Call {
  51. mr.mock.ctrl.T.Helper()
  52. return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DelUser", reflect.TypeOf((*MockUserRepo)(nil).DelUser), ctx, userID)
  53. }
  54. // GetUser mocks base method.
  55. func (m *MockUserRepo) GetUser(ctx context.Context, userID int) (*entity.User, bool, error) {
  56. m.ctrl.T.Helper()
  57. ret := m.ctrl.Call(m, "GetUser", ctx, userID)
  58. ret0, _ := ret[0].(*entity.User)
  59. ret1, _ := ret[1].(bool)
  60. ret2, _ := ret[2].(error)
  61. return ret0, ret1, ret2
  62. }
  63. // GetUser indicates an expected call of GetUser.
  64. func (mr *MockUserRepoMockRecorder) GetUser(ctx, userID interface{}) *gomock.Call {
  65. mr.mock.ctrl.T.Helper()
  66. return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockUserRepo)(nil).GetUser), ctx, userID)
  67. }

编写单元测试

  1. gomock
的单元测试编写起来也很方便,只需要调用
  1. EXPECT()
方法,将需要 mock 的接口对应需要的返回值就可以了。我们直接来看例子:
  1. package service
  2. import (
  3. "context"
  4. "testing"
  5. "github.com/golang/mock/gomock"
  6. "github.com/stretchr/testify/assert"
  7. "go-demo/m/unit-test/entity"
  8. "go-demo/m/unit-test/mock"
  9. )
  10. func TestUserService_AddUser(t *testing.T) {
  11. ctl := gomock.NewController(t)
  12. defer ctl.Finish()
  13. mockUserRepo := mock.NewMockUserRepo(ctl)
  14. userInfo := &entity.User{Username: "LinkinStar"}
  15. // 无论对 AddUser 方法输入任意参数,均会返回 userInfo 信息
  16. mockUserRepo.EXPECT().AddUser(gomock.Any(), gomock.Any()).Return(nil)
  17. userService := NewUserService(mockUserRepo)
  18. err := userService.AddUser(context.TODO(), userInfo.Username)
  19. assert.NoError(t, err)
  20. }
  21. func TestUserService_GetUser(t *testing.T) {
  22. ctl := gomock.NewController(t)
  23. defer ctl.Finish()
  24. userID := 1
  25. username := "LinkinStar"
  26. mockUserRepo := mock.NewMockUserRepo(ctl)
  27. // 只有当对于 GetUser 传入 userID 为 1 时才会返回 user 信息
  28. mockUserRepo.EXPECT().GetUser(context.TODO(), userID).Return(&entity.User{
  29. ID: userID,
  30. Username: username,
  31. }, true, nil)
  32. userService := NewUserService(mockUserRepo)
  33. userInfo, err := userService.GetUser(context.TODO(), userID)
  34. assert.NoError(t, err)
  35. assert.Equal(t, username, userInfo.Username)
  36. }

与之前一样,我们依旧使用

  1. github.com/stretchr/testify
做断言来验证最终结果。可以看到,单元测试编写起来并不难。

优化

当然,如果我们每次修改接口或者新增接口都需要重新执行一次命令,一个文件还好,当有很多文件的时候肯定是非常困难的。所以我们需要使用 go:generate 来优化一下。

我们可以在需要 mock 的接口上方加入注释(注意这里写的路径要和实际路径相符合):

  1. //go:generate mockgen -source=./user.go -destination=../mock/user_mock.go -package=mock
  2. type UserRepo interface {
  3. AddUser(ctx context.Context, user *entity.User) (err error)
  4. DelUser(ctx context.Context, userID int) (err error)
  5. GetUser(ctx context.Context, userID int) (user *entity.User, exist bool, err error)
  6. }

然后只需要使用命令

  1. go generate ./...

就可以生成全部的 mock 嘞,所以及时文件很多,只需要利用好 go:generate 也能一次搞定。

以上就是Golang如何实现单元测试中的逻辑层的详细内容,更多关于Golang如何实现单元测试中的逻辑层的资料请关注九品源码其它相关文章!