目录

  • go test 命令
  • 测试函数
  • go test 的参数
  • 基准测试
  • 示例函数
  • 参考与拓展


go test 命令

go test 用于测试 go 编写的代码程序,要求将测试的文件命名为 *_test.go,这样命名的文件不会被 go build 构建成包的一部分,但是会被 go test 进行测试。

*_test.go 中有三种类型的函数

  1. 测试函数:以 Test 为函数名的前缀,用于测试程序的逻辑行为。
  2. 基准测试函数:以 Benchmark 为函数名的前缀,用于测试程序的性能。
  3. 示例函数:以 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 指定编译参数 go test命令_后端


当我们输入 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 的参数

上面我们看到,实际上我们有两个测试函数,TestAddTestSub,执行 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
  1. 结果会输出相关的信息,其中 BenchmarkAdd-8 中的 8 表示 GOMAXPROCS 的值
  2. 后面紧跟着的 1000000000 ,表示一共执行了 1000000000,这个次数就像前面说的是从小到大递增但随机的
  3. 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

示例函数的作用:

  1. 作为文档和 godoc 一起使用
  2. go test 可以直接运行示例函数,并且,如果你的示例函数有 //Output: 格式的注释,则会检查测试的输出结果和注释结果是否一致
  3. 可以制作类似 go playground 的演练场

参考与拓展

  1. 《go 语言圣经》
  2. go 官方文档
  3. Code Coverage in GoLang:go 中的代码覆盖率怎么编写。