《Go 单元测试从入门到覆盖率提升》(二)
Golang相关测试框架
在 Go 语言里,最常用的测试框架有:自带的 testing 包、GoConvey 和 testify。
1、Go自带的testing包
testing 包是官方内置的,无需额外安装,也是大多数项目的首选。
(1)单元测试
-
测试文件必须以
_test.go结尾,例如:calc_test.go -
测试函数必须以
Test开头(推荐写成Test+函数名,更直观) -
测试函数签名固定:
func TestXxx(t *testing.T),不能有返回值 -
测试函数内部用
t.Errorf/t.Fatal等方法输出错误- t.Errorf:标记测试失败,但 继续执行后面的测试代码。
- t.Fatal:标记测试失败,并 立即中止当前测试函数。
# 执行当前目录下所有 *_test.go 文件 go test -v # 只执行指定文件中的测试 go test -v calc_test.go calc.go # 执行指定的测试函数(-count=1 表示禁用缓存,强制重新运行) go test -v -run TestAdd calc_test.go calc.go -count=1
(2)性能测试
-
文件命名:和单测一样,文件必须以
_test.go结尾,这样go test才能识别。 -
函数命名:基准测试函数要以
Benchmark开头,且必须是导出函数,比如BenchmarkAdd。 -
函数签名:基准测试函数必须接收一个
*testing.B类型的参数,不能有返回值。 -
重置计时器:
b.ResetTimer()用来清空前面初始化代码的耗时,保证只统计真正的测试部分。 -
循环执行:基准测试里要用
for i := 0; i < b.N; i++ { ... },被测的代码放在循环里。 -
b.N 的作用:Go 框架会自动调整
b.N,让测试至少运行 1 秒左右,这样结果更稳定。最终输出的是平均每次运行的耗时(比如0.25 ns/op表示每次 0.25 纳秒)。
在calc文件夹的calc_test.go文件中新增BenchmarkAbs和BenchmarkAdd方法。

基准测试的目标就是 尽可能准确地衡量一段代码的性能,包括运行时间、内存分配等。
-
核心思路:同一段代码运行很多次(
b.N次),用「总耗时 ÷ b.N」算出平均每次执行的耗时。 -
b.N 的作用:
b.N由 Go 测试框架自动决定,会动态调整,保证测试至少运行 1 秒左右,避免样本太少导致结果不准。 -
运行过程:当执行
go test -bench=.时,框架会先用很小的b.N(比如 1、2、5、19)试跑几次,估算耗时,再逐步增大b.N,直到结果稳定。 -
输出示例:比如报告里写
1000000000 0.25 ns/op,意思是运行了 10 亿次,每次平均耗时 0.25 纳秒。


# 运行所有基准测试 go test -bench=. # 运行所有基准测试并显示内存分配情况 go test -bench=. -benchmem # 只运行基准测试,不运行单元测试 go test -run=none -bench=. # 指定测试时间 go test -bench=. -benchtime=3s # 指定运行轮数 go test -bench=. -count=3 # 组合使用多种参数 go test -run=none -bench=. -benchmem -benchtime=3s -count=3
2、GoConvey
go test 命令就能照常运行;如果你想要更直观的体验,可以直接运行 goconvey,然后在浏览器里访问 http://localhost:8080,就能看到实时的测试结果展示。
相比起纯粹使用标准库的 testing 包,Convey 让单元测试的书写和阅读都更流畅,尤其适合需要频繁跑测试、调试逻辑的开发过程。
(1)安装依赖:go get github.com/smartystreets/goconvey
(2)demo


3、testify
Testify 是 Go 语言生态里非常常用的一个 断言风格测试框架。它不仅提供了开发者最常用的断言方法(让测试代码更简洁、可读性更强),还额外封装了三个核心功能模块:
-
Assertions(断言):内置了丰富的断言方法,避免重复编写样板式判断逻辑。
-
Suite(测试套件):支持将一组相关测试组织在一起,方便共享初始化、清理等逻辑。
-
Mock(模拟):提供强大的 Mock 能力,方便在单元测试中模拟依赖对象或外部接口。
借助 Testify,我们可以用更接近自然语言的方式编写测试,不仅提高开发效率,也让测试结果更易于理解。
(1)依赖安装:go get -u -v github.com/stretchr/testify
(2)测试用例
① assert包提供了断言工具,在执行时会将case标记为失败,但程序不会退出,而是继续往下执行。
package testify import ( "testing" "github.com/stretchr/testify/assert" ) func Test_assert(t *testing.T) { a := 2 b := 3 // 第一个断言失败(故意写错期望值) assert.Equal(t, 6, a+b, "第一个断言:2+3应该等于5,不是6") // 程序继续执行下面的断言 t.Log("这行代码被执行了,说明程序没有中断") // 第二个断言通过 assert.Equal(t, 5, a+b, "第二个断言:2+3应该等于5") // 第三个断言也会执行 assert.True(t, a < b, "第三个断言:2应该小于3") t.Log("所有断言都已执行完毕") }

② require包提供与asser包相同的全局函数,与assert不同的是,require会终止当前测试。
package testify import ( "testing" "github.com/stretchr/testify/require" ) func Test_require(t *testing.T) { name := "Tom" age := 18 // 这个会失败并停止测试 require.Equal(t, "dazuo", name, "测试会在这里停止") require.Equal(t, 18, age, "测试会在这里停止") // 这行不会被执行 t.Log("这行代码不会被执行") }

③ mock包提供了一种轻松编写模拟对象的机制,可以在编写测试代码时替代实际对象使用模拟对象。
func Test_mock(t *testing.T) { // 创建模拟对象 mockS := &mockStorage{} // 设置对Store方法期望的调用,参数必须匹配;返回该方法调用时的返回值 mockS.On("Store", "name", "Tom").Return(1, nil).Once() result, err := mockS.Store("name", "Tom") assert.NoError(t, err) assert.Equal(t, 1, result) mockS.AssertExpectations(t) // 断言所有预期的调用都发生了 }

若该方法没有被调用:
func Test_mock(t *testing.T) { // 创建模拟对象 mockS := &mockStorage{} // 设置对Store方法期望的调用,参数必须匹配;返回该方法调用时的返回值 mockS.On("Store", "name", "Tom").Return(1, nil).Once() mockS.AssertExpectations(t) // 断言所有预期的调用都发生了 }

浙公网安备 33010602011771号