大家好,我是 frank。 欢迎大家点击标题下方蓝色文字「Golang 语言开发栈」关注公众号。 设为星标,第一时间接收推送文章。 文末扫码,加群一起学 Golang 语言。
01
介绍
我们使用 Golang 语言开发的项目,怎么保证逻辑正确和性能要求呢?也就是说我们如何测试我们的 Golang 代码呢?在 Golang 语言中,可以使用标准库 testing 包编写单元测试和基准测试,使用 go test
命令执行单元测试和基准测试的代码。本文我们介绍在 Golang 语言中怎么编写测试代码。
02
命名规范
在 Golang 语言中编写测试代码,需要遵循一些命名规范,包含文件名、包名、函数(方法)名和变量名。
文件名和包名
测试文件名以 _test.go
结尾,go test
工具可以遍历以 _test.go
结尾的文件,执行测试函数。而 go build
和 go run
会忽略以 _test.go
结尾的文件,文件名开头一般是被测试函数所在的文件名。
包名一般和被测试文件的包名相同,这样即可以测试被测试文件的可导出函数和不可导出函数。
函数名和方法名
测试函数(方法)名必须以 Test、Benchmark 和 Example 开头,并且必须是可导出函数。函数名一般是被测试函数名,首字母大写。如果我们需要给同一个函数编写多个测试函数,可以在函数名后接上测试函数的场景,例如:TestXxxxXxxx
。
变量名
测试函数(方法)的变量名,Golang 语言和 go test
工具没有明确的约束,但是,社区针对输出结果有一些规范供大家参考。在编写单元测试代码时,一般会得到一个实际输出结果,和一个我们预期的输出结果做对比。针对这两个变量,社区的变量名规范是 got/want
或 expected/actual
。
03
编写测试代码
单元测试
所谓单元测试,顾名思义就是对单元进行测试,一般进行测试的单元是一个最小的单元,在 Golang 语言中,最小的单元就是指一个函数或方法。
单元测试的函数,函数名以 Test
开头,例如:TestXxx
。参数必须是 *testing.T
类型,可以使用该类型的方法记录测试信息和测试状态。例如,一般使用 Log
和 Logf
记录测试信息,使用 Error
、Errorf
、Fatal
和 Fatalf
方法记录测试状态,该类型的更多方法可以阅读官方文档。
被测试函数:
代码语言:javascript复制func Sum(a, b int) int {
return a b
}
测试函数:
代码语言:javascript复制func TestSum(t *testing.T) {
a, b := 1,2
rst := Sum(a, b)
if rst == 3 {
t.Logf("expected=%d, actual=%d", 3, rst)
} else {
// t.Errorf("expected=%d, actual=%d", 3, rst)
t.Fatalf("expected=%d, actual=%d", 3, rst)
}
t.Log("done")
}
阅读上面这段代码,是我们编写的 Sum 函数的单元测试,给定 a, b 两个变量作为 Sum 函数的输入参数,此外,我们还可以使用表格测试发,给定一组被测试函数的输入参数,限于篇幅,本文不准备花费篇幅介绍。
使用 go test
命令执行以上单元测试的代码:
go test
PASS
ok learn_go/lesson27 0.555s
go test
命令遍历所有 _test.go
结尾的文件,执行文件中所有的测试函数。此外,go test
支持一些参数,例如,-v
输出测试函数的运行详情;-run
指定执行的测试函数;-count
指定执行次数。
此外,使用参数 --coverprofile
统计单元测试的覆盖率。
go test --coverprofile=func.cover
PASS
coverage: 100.0% of statements
ok learn_go/lesson27 0.499s
阅读上面的执行结果,可以发现我们编写的单元测试覆盖率为 100%。
如果我们想要查看详细的覆盖率统计结果,我们可以执行以下命令生成 html
文件,使用浏览器打开生成的 html
文件,可以查看详细的单元测试覆盖率统计结果。
go tool cover -html=func.cover -o func_cover.html
运行以上命令,会生成一个名为 func_cover.html
的文件,我们可以使用浏览器打开它,查看详细的单元测试覆盖率统计结果。
基准测试
在 Golang 语言中,可以使用基准测试查看代码的性能。基准测试的函数名以 Benchmark
开头,例如:BenchmarkXxx
。参数必须是 *testing.B
类型,函数体中 for 循环的条件,以 b.N
作为循环次数,它是基准测试框架提供的,它在 Golang 运行时动态调整,通过多次测试,得到性能评估结果。
示例代码:
代码语言:javascript复制func BenchmarkSum(b *testing.B) {
for i := 0; i < b.N; i {
Sum(1, 2)
}
}
我们可以使用 go test
工具执行以上基准测试的代码,基准测试函数不会自动执行,必须使用参数 -bench
。
go test -bench=".*"
goos: darwin
goarch: amd64
pkg: learn_go/lesson27
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkSum-16 1000000000 0.2325 ns/op
PASS
ok learn_go/lesson27 0.748s
阅读上面的执行结果,我们主要介绍一下 BenchmarkXxx-n
这一行的意思。这一行共有三列,第一列 BenchmarkSum-16
分别代表基准测试的函数名和参与基准测试的 CPU 线程数,默认是 GOMAXPROCS 的值。第二列 1000000000
表示基准测试循环执行的次数。第三列 0.2325 ns/op
表示每次循环的平均执行耗时是 0.2325
纳秒,该值越小,说明代码性能越高。
除了 b.N
之外,还有几个关于性能测试时间计数的方法,例如:b.ResetTimer()
、b.StopTimer()
和 b.StartTimer()
,我们可以根据我们的测试场景,灵活使用。
此外,go test
工具关于基准测试的参数,除了参数 -bench
之外,还有 -benchmem
统计内存分配;-cpu
指定参与执行基准测试的 CPU 线程数;-benchtime
指定测试时间和循环次数,其中值的单位为 s
表示指定执行多少秒,单位为 x
表示指定循环执行次数;-timeout
指定基准测试函数执行的超时时间。
04
总结
本文我们介绍怎么编写测试代码,包含单元测试和基准测试。特别需要注意的是一些命名规范。
养成编写测试代码的习惯,不仅可以降低代码逻辑的错误率,而且在多人开发中,还可以提升联调效率和提测通过率。