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
相关推荐
zzzll11112 小时前
SpringBoot 入门与实践指南
java·spring boot·后端
newerp2 小时前
Go :结构体嵌入、sort.Interface 与 io.Reader/Writer
后端
SamDeepThinking2 小时前
如何维持缓存的一致性?
后端·面试·程序员
泡海椒2 小时前
还在手动把 Postman 和浏览器的 curl 转成 Java 代码?这个框架让你一键粘贴直接用
后端
用户8181870627462 小时前
第13章 死锁诊断实战
后端
晨曦中的暮雨2 小时前
Redis 有哪些常见数据结构?它们的底层实现和使用场景是什么?
java·后端·八股
枫叶V2 小时前
Prompt Injection 防不住怎么办?从 Source-Sink 模型设计 Agent 安全边界
后端·agent
用户8181870627462 小时前
第14章 线程池 RejectedExecutionException 与拒绝策略源码
后端
Leo2822 小时前
LangGraph:把 Agent 从“会回答”推进到“可控执行”的工程架构
后端
铁皮饭盒3 小时前
网页端OCR, 加载6mb大小模型, 又快又准, 百度这次真香
前端·后端