likes
comments
collection
share

go test与gomock单元测试使用示例

作者站长头像
站长
· 阅读数 6

一、背景和意义

过于依赖集成测试可能存在测试流程较长以及某些场景/条件覆盖不到的问题,相比之下单元测试则是提升测试速度与程序质量的重要工具。go test是go语言官方的单元测试工具,可实现简单的单元测试功能。而对于一些大量使用依赖注入的复杂系统,做单元测试是总少不了mock框架,而go语言也提供了gomock这一官方的mock框架。

本文将提供一个go test与gomock结合做单元测试的示例。

二、go test的使用

创建一个空目录,执行如下命令创建go模块:

go mod init go-test-demo

然后创建文件 math/math.go:

package math

func Add(a, b int) int {
   return a + b
}

再创建对应的测试文件 match/math_test.go

package math

import "testing"

func TestAdd(t *testing.T) {
   result := Add(2, 3)
   expected := 5
   if result != expected {
      t.Errorf("Add(2, 3) return %d, expected: %d", result, expected)
   }
}

创建完这两个文件之后执行命令:

go test ./math

命令将会输出:

ok      go-test-demo/math       0.097s

三、不同包下的go test使用

刚才创建了一个math包,现在再创建另外一个包。先创建文件 list/sum_list.go:

package list

import "go-test-demo/math"

func SumList(list []int) int {
   sum := list[0]
   for i := 1; i < len(list); i++ {
      sum = math.Add(sum, list[i])
   }
   return sum
}

再创建对应的测试代码文件 list/sum_list_test.go:

package list

import "testing"

func TestSumList(t *testing.T) {
   result := SumList([]int{2, 3, 4, 5})
   expected := 14
   if result != expected {
      t.Errorf("SumList(2, 3, 4, 5) return %d, expected: %d", result, expected)
   }
}

如下命令将执行 list下的单元测试:

go test ./list

执行完命令将会输出

ok      go-test-demo/list       0.070s

如果想执行两个包下的单元测试,则使用命令:

go test ./list ./math

将会输出:

ok      go-test-demo/list       0.094s
ok      go-test-demo/math       0.104s

如果想要执行所有包下的单元测试,则是使用命令:

go test ./...

四、需要使用mock的场景

在复杂系统中,经常涉及到依赖注入,这种情况下需要用mock。举个例子,创建票务服务相关的代码文件 biz/ticket_service.go:

package biz

type TicketService interface {
   CanBuy(userId int, amount int) bool
}

type TickerServiceImpl struct {
   financeService FinanceService
   priceService   PriceService
}

func NewTicketService(financeService FinanceService, priceService PriceService) TicketService {
   return &TickerServiceImpl{
      financeService: financeService,
      priceService:   priceService,
   }
}

func (t *TickerServiceImpl) CanBuy(userId int, amount int) bool {
   price := t.priceService.GetPrice("ticket")
   balance := t.financeService.GetAccountBalance(userId)
   return price*amount <= balance
}

票务服务需要依赖财务服务 FinanceService 和价格服务 PriceService 。财务服务的代码文件是 biz/finance_service.go:

package biz

type FinanceService interface {
   GetAccountBalance(userId int) int
}

价格服务的代码文件是 biz/price_service.go:

package biz

type PriceService interface {
   GetPrice(productName string) int
}

五、gomock的使用

这里将以前面提到的票务服务的例子介绍gomock的使用,先安装gomock:

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

使用 mockgen 工具生成mock代码:

mockgen -destination="mocks/mock_finance_service.go" -source ./biz/finance_service.go
mockgen -destination="mocks/mock_price_service.go" -source ./biz/price_service.go

执行结束之后,可以看到代码目录下生成了mocks/mock_finance_service.go和mocks/mock_price_service.go两个文件,接下来再执行添加依赖的命令:

go mod tidy

创建测试用例文件 test/biz/ticket_service_test.go:

package biz

import (
   "github.com/golang/mock/gomock"
   "go-test-demo/biz"
   mock_biz "go-test-demo/mocks"
   "testing"
)

func TestCanBuy(t *testing.T) {
   mockCtrl := gomock.NewController(t)
   defer mockCtrl.Finish()

   // 先设置依赖服务期望的返回结果
   userId := 111
   financeService := mock_biz.NewMockFinanceService(mockCtrl)
   financeService.EXPECT().GetAccountBalance(userId).Return(100).AnyTimes()
   priceService := mock_biz.NewMockPriceService(mockCtrl)
   priceService.EXPECT().GetPrice("ticket").Return(10).AnyTimes()

   ticketService := biz.NewTicketService(financeService, priceService)
   amount := 10
   result := ticketService.CanBuy(userId, amount)
   if result != true {     // 一张票价格10,用户余额100,预期能买10张,即返回true
      t.Errorf("Expect user %d can by %d tickets, buy actually is not", userId, amount)
   }

   ticketService = biz.NewTicketService(financeService, priceService)
   amount = 11
   result = ticketService.CanBuy(userId, amount)
   if result != false {    // 预期不能买11张,即返回false
      t.Errorf("Expect user %d can not by %d tickets, buy actually is not", userId, amount)
   }
}

执行测试用例:

go test ./test/...

运行结果:

ok      go-test-demo/test/biz   0.085s