足够完善的测试可以避免大部分事故的发生,本文会简要介绍一下Go语言在测试环节的单元测试和基准测试。
1、单元测试
单元测试主要包括输入、测试单元、输出以及校对,其中单元的概念比较广,包括接口、函数、模块等,而通过最后的校对,我们可以保证代码的功能与我们预期的一致。
单元测试一方面可以保证质量,在整体覆盖率足够的情况下,一定程度上既保证了新功能本身的正确性,又为破坏原有代码的正确性;另一方面可以提高效率,在代码存在bug的情况下,通过编写单元测试可以快速地定位错误,从而将其修复。
基本语法
下面介绍一下go的基本单元测试语法。
- 所有的测试文件名为
*_test.go,与测试的代码放在同一个包中。 - 测试函数的头部为
func TestXxx(t *testing.T),以Test开头并且连接的第一个字母大写,xxx是被测函数的描述,T 是传给测试函数的结构类型,用来管理测试状态,支持格式化测试日志,如 t.Log,t.Error,t.ErrorF 等。 - 将初始化逻辑放入
TestMain函数中,如果存在该函数,则将测试交由其调度,反之测试程序直接执行各项测试。
运行的命令格式如下:go test [falg] [packages]
举一个简单的例子:在even.go有如下两个函数:
package even
func Even(i int) bool {
return i%2 == 0
}
func Odd(i int) bool {
return i%2 == 1
}
在even_test.go中,有如下测试函数:
package even
import (
"testing"
)
func TestAdd(t *testing.T) {
if !Even(10) {
t.Error(" 10 must be even!")
}
if Even(7) {
t.Error(" 7 is not even!")
}
}
func TestOdd(t *testing.T) {
if !Odd(11) {
t.Error(" 11 must be odd!")
}
if Odd(10) {
t.Error(" 10 is not odd!")
}
}
通过运行go test -v(-v显示每个测试用例的结果),结果如下:
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
=== RUN TestOdd
--- PASS: TestOdd (0.00s)
PASS
ok test/even 0.511s
添加一个TestMain函数,函数体如下:
func TestMain(m *testing.M) {
fmt.Println("before test.")
code := m.Run()
fmt.Println("after test.")
os.Exit(code)
}
再次运行go test -v,结果如下:
before test.
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
=== RUN TestOdd
--- PASS: TestOdd (0.00s)
PASS
after test.
ok test/even 0.508s
相关事项
-
首先要注意的是,
_test程序不会被普通的 Go 编译器编译,所以当放应用部署到生产环境时它们不会被部署;只有go test会编译所有的程序:普通程序和测试程序。 -
由于测试需要具体的输入用例且不可能测试到所有的用例(非常像一个无穷的数),所以我们必须对要使用的测试用例思考再三。
至少应该包括:
- 正常的用例
- 反面的用例(错误的输入,如用负数或字母代替数字,没有输入等)
- 边界检查用例(如果参数的取值范围是 0 到 1000,检查 0 和 1000 的情况)
-
衡量一个代码是否经过了较好的测试的标准之一是覆盖率,即测试代码占所有代码的比例。使用
--coveer可以显示测试的覆盖率。在实际的项目中,一般覆盖率在50%~60%既可;而对于要求较高的项目,如资金型的项目,则需要达到80%。
2、基准测试
Go语言还提供了基准测试的框架,基准测试是指测试一段程序的运行性能和消耗CPU的程度。而在我们的实际项目开发中,经常会遇到代码性能瓶颈,为了定位问题经常要对代码做性能分析,这就用到了基准测试。
基准测试的使用方法类似于单元测试,放在*_test.go文件中,函数头部如下:func BenchmarkXxx(b *testing.B)。
在fib包中,有一个函数如下:
package fib
func Fib(n int) int {
switch n {
case 0:
return 0
case 1, 2:
return 1
default:
return Fib(n-1) + Fib(n-2)
}
}
在fib_test,go文件中有两个基准测试函数:
package fib
import (
"testing"
)
func BenchmarkFib(b *testing.B) {
for i := 0; i < 40; i++ {
Fib(20)
}
}
func BenchmarkFib2(b *testing.B) {
for i := 0; i < 40; i++ {
Fib(30)
}
}
默认go test不会运行基准测试函数,需要加上-bench=xxx,使用正则表达式进行匹配。使用go test -bench .来运行所有基准函数(应该是go test -bench=.,但这个命令不可以,原因还在探索),结果如下:
goos: windows
goarch: amd64
pkg: test/fib
cpu: AMD Ryzen 5 5600H with Radeon Graphics
BenchmarkFib-12 1000000000 0.0005742 ns/op
BenchmarkFib2-12 1000000000 0.1067 ns/op
PASS
ok test/fib 1.119s
可以据此分析何处需要优化,快速定位瓶颈。
结语
通过合理、全面的测试,我们不仅可以在开发的过程中及时的发现、定位错误,还可以发现性能瓶颈,依靠数据来开展优化,将会大大提高效率。因此,每一个开发人员都应该掌握测试的方法,并灵活运用在开发中。