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
相关推荐
小码哥_常2 小时前
Spring Boot 中JWT登录授权+无感刷新,看这篇就够了!
后端
码农BookSea3 小时前
深度解析Skills:从Prompt到能力复用的技术革命
后端·ai编程
计算机毕设指导63 小时前
基于SpringBoot校园学生健康监测管理系统【源码文末联系】
java·spring boot·后端·spring·tomcat·maven·intellij-idea
希望永不加班3 小时前
SpringBoot 数据库连接池配置(HikariCP)最佳实践
java·数据库·spring boot·后端·spring
夕颜1114 小时前
写 SIP 服务后台前,先把 SIP 和 PSTN 搞清楚
后端
码农BookSea4 小时前
为什么ChatGPT能听懂你说的话?Embedding技术揭秘
后端·openai
黑牛儿4 小时前
MySQL 索引实战详解:从创建到优化,彻底解决查询慢问题
服务器·数据库·后端·mysql
程序员飞哥4 小时前
到底Java 适不适合做 AI 呢?
后端·程序员·全栈
码事漫谈5 小时前
AI提效,到底能强到什么程度?
前端·后端
IT_陈寒5 小时前
React hooks依赖数组这个坑差点把我埋了
前端·人工智能·后端