应该避免在单元测试中使用gomock.any(),使用gomock.any()让你的单元测试更容易通过的同时也会降低单元测试的可信度。

举个例子

业务描述

一个简单的新增学生信息功能,记录一个学生的时候,除了基本信息外,还需要从评价系统获取该学生的评价信息存入数据库。

domain

type Student struct {
  ID         uint   `json:"id"`
  UUID       string `json:"uuid"`
  Age        int    `json:"age"`
  Name       string `json:"name"`
  Assessment string `json:"assessment"` // 评价信息
}

usecase层

type StudentUsecase struct {
  repo       domain.IStudentUsecase
  assService infra.AssessmentService
}

func (s *StudentUsecase) Create(student domain.Student) error {
  // 从评价服务获取该学生的评价信息
  assessment, err := s.assService.GetAssessment(student.UUID)
  if err != nil {
    return err
  }
  student.Assessment = assessment
  return s.repo.Create(student)
}

usecase测试用例

func TestStudentUsecaseCreate(t *testing.T) {
  ctl := gomock.NewController(t)
  defer ctl.Finish()
  repo:=mocks.NewMockIStudentRepo(ctl)
  assService :=mocks2.NewMockAssessmentService(ctl)
  student:=domain.Student{
    ID:   100,
    UUID: "uuid1",
    Age:  24,
    Name: "seven",
  }

  assService.EXPECT().GetAssessment(gomock.Any()).Return("a good student",nil)
  repo.EXPECT().Create(gomock.Any()).Return(nil)

  usecase := &StudentUsecase{
    repo:       repo,
    assService: assService,
  }
  if err:=usecase.Create(student);err!=nil{
    t.Fail()
  }
}

这样的测试用例能正确执行,也能增加项目的整体覆盖率,但是这是一个假的测试用例,这种写法忽略了一个重要的事情,usecase.Create的入参校验。正确的写法应该如下:

func TestStudentUsecaseCreate2(t *testing.T) {
  ctl := gomock.NewController(t)
  defer ctl.Finish()
  repo:=mocks.NewMockIStudentRepo(ctl)
  assService :=mocks2.NewMockAssessmentService(ctl)
  student:=domain.Student{
    ID:   100,
    UUID: "uuid1",
    Age:  24,
    Name: "seven",
  }

  assService.EXPECT().GetAssessment("uuid1").Return("a good student",nil)
  repo.EXPECT().Create(gomock.Eq(domain.Student{
    ID:   100,
    UUID: "uuid1",
    Age:  24,
    Name: "seven",
    Assessment: "a good student",
  })).Return(nil)

  usecase := &StudentUsecase{
    repo:       repo,
    assService: assService,
  }
  if err:=usecase.Create(student);err!=nil{
    t.Fail()
  }
}

这个测试用例包含了两次参数校验,一个是学生的UUID,另一个是Assessment信息。这样的单元测试才是有意义的。

总结

要对自己写的单元测试负责,单元测试和业务代码一样重要。让我们从少用甚至是不用gomock.any()开始。