目录
- go test 命令
- 测试函数
- go test 的参数
- 基准测试
- 示例函数
- 参考与拓展
go test 命令
go test
用于测试 go 编写的代码程序,要求将测试的文件命名为 *_test.go
,这样命名的文件不会被 go build
构建成包的一部分,但是会被 go test
进行测试。
在 *_test.go
中有三种类型的函数
- 测试函数:以
Test
为函数名的前缀,用于测试程序的逻辑行为。 - 基准测试函数:以
Benchmark
为函数名的前缀,用于测试程序的性能。 - 示例函数:以
Example
为函数名的前缀,提供示例文档。
go test
命令会执行所有 *_test.go
的函数,生成一个临时的 main
包用于调用相应的测试函数。
测试函数
刚刚提到了,测试函数的作用就是测试函数的逻辑是否正确,比如有一个加法函数,用 a+b 能否得到正确的求和结果,当然这是非常简单的,下面我们就来从简单的入手,看看测试函数的效果。
要进行一个函数的测试,必然要先有一个函数,我们在 main 包中创建一个 calculation.go 的文件,并在其中写一个非常简单的加法函数。
// main/calculation.go
package main
func add(a, b int) int {
return a + b
}
接下来我们就来测试这个函数写对没(虽然显而易见的是它是正确的),但是一些复杂逻辑的代码中,可能就没这么容易一眼看出来,所以这也是写测试函数的原因。
写测试函数的时候,要放在单独的测试文件中,前面讲过这个文件的命名格式是有要求的 *_test.go
,这里我们命名为 calculation_test.go
。
下面的代码中我标注了几个点,我们会挨个来讲讲其中的作用。
// main/calculation_test.go
package main
// 1.
import "testing"
// 2.
func TestAdd(t *testing.T) {
// 3.
var testTable = []struct {
a int
b int
sum int
}{
{1, 2, 3},
{11, 21, 32},
{104, 23, 127},
}
for _, test := range testTable {
// 4.
if ans := add(test.a, test.b); ans != test.sum {
t.Errorf("add(%d, %d) != %d", test.a, test.b, test.sum)
}
}
}
首先是 1. ,非常明显,测试函数需要引入 “testing” 这个测试包
然后是 2. ,首先,刚刚我们讲了测试函数的函数名是 Test
为前缀,后面接的描述这个函数作用的单词也要以大写字母开头,比如这里,我要测试 add()
函数,所以我的测试函数取名为 TestAdd
接着,我们看到这个 TestAdd
中包含一个参数 t *testing.T
,这是测试函数专门的参数,如果是基准测试函数,则里面的参数是 b *testing.B
,如果是示例函数,则无需参数。
接着是 3. 在这里我用到了表格驱动的测试,结构体里面的那些实例就是表格的内容(如,{1, 2,3}),一个显而易见的好处就是,我们可以随时往这个表格里面添加测试需要的实例。
更多的你可以参考这篇文章 Go 如何编写简洁测试 – 表格驱动测试。
最后是 4. ,在表格中,a,b 对应的字段是 add
函数的输入参数,而 sum 字段则是理论上的结果,通过 for range
遍历这个表格结构体,用 ans 来接收 add
函数的返回值,将这个值与理论值 sum 进行比较,如果相同则说明函数是正确的,否则不正确。
我们来看看最后代码运行的结果。
当我们输入 go test
的下一行是 PASS
,说明全部的函数通过了,并且显示了函数运行的时间。
同样,我们不妨在 main/calculation.go
中增加一个减法函数用以演示如果函数逻辑错误会是什么样。当然,我们先从正确的示例开始,然后在故意修改成错误的。
// 这是一个正确的 sub 函数
func sub(a, b int) int {
return a - b
}
// 这是 sub 函数的测试函数
func TestSub(t *testing.T) {
var testTable = []struct {
a int
b int
subAns int
}{
{2, 1, 1},
{11, 21, -10},
{104, 23, 81},
}
for _, test := range testTable {
if ans := sub(test.a, test.b); ans != test.subAns {
t.Errorf("add(%d, %d) != %d,而是等于了 %d", test.a, test.b, test.subAns, ans)
}
}
}
在终端数输入 go test 会显示正确,通过测试
PS D:\code\go_project\study\gotest_study> go test
PASS
ok gotest 0.494s
现在我们来修改一下,我们将 sub
中的 a - b 改成 a + b,这显然不是减法的逻辑。然后我们在拿到 TestSub
中去测试,注意,TestSub
的表格中,我已经写入了 {2, 1, 1} … 这些测试数据,其中每一个集合的第三个数据是理论上的正确值。
再次在终端中输入 go test
,会发现报错了,报错的主体就是 t.Errorf
输出的内容
PS D:\code\go_project\study\gotest_study> go test
--- FAIL: TestSub (0.00s)
calculation_test.go:44: add(2, 1) != 1,而是等于了 3
calculation_test.go:44: add(11, 21) != -10,而是等于了 32
calculation_test.go:44: add(104, 23) != 81,而是等于了 127
FAIL
exit status 1
FAIL gotest 0.468s
go test 的参数
上面我们看到,实际上我们有两个测试函数,TestAdd
和 TestSub
,执行 go test
命令的时候,所有的测试函数都会被执行,但是如果里面有错误逻辑的函数,则报错时只显示错误信息,而另一个正确的函数并没有显示出相关信息,在这里我们可以用 go test -v
来显示出所有的被测试函数的信息。
PS D:\code\go_project\study\gotest_study> go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
=== RUN TestSub
calculation_test.go:44: add(2, 1) != 1,而是等于了 3
calculation_test.go:44: add(11, 21) != -10,而是等于了 32
calculation_test.go:44: add(104, 23) != 81,而是等于了 127
--- FAIL: TestSub (0.00s)
FAIL
exit status 1
FAIL gotest 0.438s
这里我们看到,就输出了所有测试函数的信息,不管是成功还是失败。
go test
参数中还有一个常用的,go test -run=regexp
,意思就是只对函数名满足正则表达式的函数进行测试。比如,我现在只想测试 TestAdd 函数,于是我可以输入 go test -v -run="TestAdd"
PS D:\code\go_project\study\gotest_study> go test -v -run="TestAdd"
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok gotest 0.504s
更多关于 go test 参数的信息,可以参考这个官方文档,Testing flags
基准测试
基准测试用于进行代码性能的测试,其参数为 b *testing.B
,其中 b 除了提供和 t 类似的测试方法工具外,还有一些额外的方法工具,同时还提供了 b.N 作为循环次数。
下面我们对之前的 add 函数进行基准测试。
// 在 calculation_test.go 中继续书写 benchmark 为前缀的函数
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
add(1, 2)
}
}
这里的 b.N 为循环次数,但是这个次数不是我们自己书写,而是默认从小到大的一个顺序,逐渐进行测试,具体可以参考官方文档。
下面我们在命令行中进行测试。首先,go test
命令并不能直接测试基准函数,而是要添加 -bench
这个参数,该参数后面跟一个正则表达式用于筛选要测试的基准函数,如果要测试所有的基准函数则语法是 go test -bench=.
go test -bench="BenchmarkAdd"
goos: windows
goarch: amd64
pkg: gotest
cpu: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
BenchmarkAdd-8 1000000000 0.3019 ns/op
PASS
ok gotest 1.013s
- 结果会输出相关的信息,其中 BenchmarkAdd-8 中的 8 表示 GOMAXPROCS 的值
- 后面紧跟着的 1000000000 ,表示一共执行了 1000000000,这个次数就像前面说的是从小到大递增但随机的
- 0.3019 ns/op 表示执行了 1000000000 次后的平均执行时间
示例函数
示例函数是以 Example 为函数名的前缀的函数。没有函数参数和返回值。特别的是,Example 后面接的要测试的函数名同样要大写。比如下面我要测试的是前面的 sub
函数,那么示例函数的名字就要是 ExampleSub
func ExampleSub() {
fmt.Println(sub(2, 1))
//Output:1
}
直接运行 go test
即可进行测试,这里我为了只运行 ExampleSub
,于是使用 go test -run="ExampleSub"
来执行命令。
PS D:\code\go_project\study\gotest_study> go test -run="ExampleSub"
PASS
ok gotest 0.324s
特别的,还可以看到上面的代码中有一行注释 //Output:1
,这个的作用是将示例代码运行的结果和这个注释进行比较,如果错误则报错。比如,上面对 sub
的示例函数,应该是输出 1(2-1=1),我将 //Output:1
改为 //Output:2
,则会报错。
go test -run="ExampleSub"
got:
1
want:
2
FAIL
exit status 1
FAIL gotest 0.403s
示例函数的作用:
- 作为文档和 godoc 一起使用
- go test 可以直接运行示例函数,并且,如果你的示例函数有 //Output: 格式的注释,则会检查测试的输出结果和注释结果是否一致
- 可以制作类似 go playground 的演练场
参考与拓展
- 《go 语言圣经》
- go 官方文档
- Code Coverage in GoLang:go 中的代码覆盖率怎么编写。