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

一、背景和意义

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

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

二、go test的使用

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

bash 复制代码
go mod init go-test-demo

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

go 复制代码
package math

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

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

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)
   }
}

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

bash 复制代码
go test ./math

命令将会输出:

log 复制代码
ok      go-test-demo/math       0.097s

三、不同包下的go test使用

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

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:

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下的单元测试:

bash 复制代码
go test ./list

执行完命令将会输出

log 复制代码
ok      go-test-demo/list       0.070s

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

bash 复制代码
go test ./list ./math

将会输出:

log 复制代码
ok      go-test-demo/list       0.094s
ok      go-test-demo/math       0.104s

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

bash 复制代码
go test ./...

四、需要使用mock的场景

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

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:

go 复制代码
package biz

type FinanceService interface {
   GetAccountBalance(userId int) int
}

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

go 复制代码
package biz

type PriceService interface {
   GetPrice(productName string) int
}

五、gomock的使用

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

bash 复制代码
go install github.com/golang/mock/mockgen@latest

使用 mockgen 工具生成mock代码:

bash 复制代码
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两个文件,接下来再执行添加依赖的命令:

bash 复制代码
go mod tidy

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

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)
   }
}

执行测试用例:

bash 复制代码
go test ./test/...

运行结果:

log 复制代码
ok      go-test-demo/test/biz   0.085s
相关推荐
DemonAvenger6 分钟前
TCP连接池设计与实现:提升Go应用网络性能
网络协议·架构·go
sniper_fandc2 小时前
SpringBoot系列—入门
java·spring boot·后端
Piper蛋窝8 小时前
深入 Go 语言垃圾回收:从原理到内建类型 Slice、Map 的陷阱以及为何需要 strings.Builder
后端·go
六毛的毛10 小时前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack11 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
315356691311 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
uzong11 小时前
curl案例讲解
后端
一只叫煤球的猫12 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
大鸡腿同学13 小时前
身弱武修法:玄之又玄,奇妙之门
后端
DemonAvenger13 小时前
高性能 TCP 服务器的 Go 语言实现技巧:从原理到实践
网络协议·架构·go