go test

go test是golang的轻量化单元测试工具,结合testing这个官方包,可以很方便为golang程序写单元测试和基准测试。

在之前go build命令介绍的时候说过,go build 编译包时,会忽略以“_test.go”结尾的文件。这些被忽略的_test.go文件正是go test的一部分。

在以_test.go为结尾的文件中,常见有两种类型的函数:

  • 测试函数,以Test为函数名前缀的函数,用于测试程序是否按照正确的方式运行;使用go test命令执行测试函数并报告测试结果是PASS或FAIL。
  • 基准测试(benchmark)函数,以Benchmark为函数名前缀的函数,它们用于衡量一些函数的性能;基准测试函数中一般会多次调用被测试的函数,然后收集平均执行时间。

无论是测试函数或者是基准测试函数都必须import testing

测试函数

测试函数的签名

func TestA(t *testing.T){

}

例如我们看下go的官方包 bytes.Compare 函数的测试

package test

import (
	"bytes"
	"testing"
)

var Compare = bytes.Compare

func TestCompareA(t *testing.T) {
	var b = []byte("Hello Gophers!")
	if Compare(b, b) != 0 {
		t.Error("b != b")
	}
	if Compare(b, b[:1]) != 1 {
		t.Error("b > b[:1] failed")
	}
}

我们执行下 go test,这个命令会遍历当前目录下所有的测试函数.

$ go test 
PASS
ok      github.com/wida/gocode/test  0.001s

参数-v用于打印每个测试函数的名字和运行时间:

$ go test -v
=== RUN   TestCompareA
--- PASS: TestCompareA (0.00s)
=== RUN   TestCompareB
--- PASS: TestCompareB (0.00s)
PASS
ok      github.com/wida/gocode/test  0.002s

参数-run 用于运行制定的测试函数,

$ go run -v -run "TestCompareA"
=== RUN   TestCompareA
--- PASS: TestCompareA (0.00s)
PASS
ok      github.com/wida/gocode/test  0.002s

run 后面的参数是正则匹配 -run "TestCompare" 会同时执行 TestCompareA 和 TestCompareB。

表驱动测试

在实际编写测试代码时,通常把要测试的输入值和期望的结果写在一起组成一个数据表(table),表(table)中的每条记录代表是一个含有输入值和期望值。还是看官方bytes.Compare的测试例子:

var compareTests = []struct {
	a, b []byte
	i    int
}{
	{[]byte(""), []byte(""), 0},
	{[]byte("a"), []byte(""), 1},
	{[]byte(""), []byte("a"), -1},
	{[]byte("abc"), []byte("abc"), 0},
	{[]byte("abd"), []byte("abc"), 1},
	{[]byte("abc"), []byte("abd"), -1},
	{[]byte("ab"), []byte("abc"), -1},
	{[]byte("abc"), []byte("ab"), 1},
	{[]byte("x"), []byte("ab"), 1},
	{[]byte("ab"), []byte("x"), -1},
	{[]byte("x"), []byte("a"), 1},
	{[]byte("b"), []byte("x"), -1},
	// test runtime·memeq's chunked implementation
	{[]byte("abcdefgh"), []byte("abcdefgh"), 0},
	{[]byte("abcdefghi"), []byte("abcdefghi"), 0},
	{[]byte("abcdefghi"), []byte("abcdefghj"), -1},
	{[]byte("abcdefghj"), []byte("abcdefghi"), 1},
	// nil tests
	{nil, nil, 0},
	{[]byte(""), nil, 0},
	{nil, []byte(""), 0},
	{[]byte("a"), nil, 1},
	{nil, []byte("a"), -1},
}

func TestCompareB(t *testing.T) {
	for _, tt := range compareTests {
		numShifts := 16
		buffer := make([]byte, len(tt.b)+numShifts)
		for offset := 0; offset <= numShifts; offset++ {
			shiftedB := buffer[offset : len(tt.b)+offset]
			copy(shiftedB, tt.b)
			cmp := Compare(tt.a, shiftedB)
			if cmp != tt.i {
				t.Errorf(`Compare(%q, %q), offset %d = %v; want %v`, tt.a, tt.b, offset, cmp, tt.i)
			}
		}
	}
}
$ go test -v -run "TestCompareB"
=== RUN   TestCompareB
--- PASS: TestCompareB (0.00s)
PASS
ok      github.com/wida/gocode/test  0.004s

基准测试函数

基准测试函数的函数签名如下

func BenchmarkTestB(b *testing.B) {
  
}

我们还是一官方的bytes.Compare 为例写一个基准测试

func BenchmarkComare(b *testing.B) {

	for i := 0; i < b.N; i++ {
		Compare([]byte("abcdefgh"), []byte("abcdefgh"))
	}
}

基准测试的运行 需要加 -bench参数

$ go test  -bench BenchmarkCompare
goos: linux
goarch: amd64
pkg: github.com/wida/gocode/test
BenchmarkCompare-4      30000000                35.4 ns/op
PASS
ok      github.com/wida/gocode/test  1.111s

报告显示我们的测试程序跑了30000000次,每次平均耗时35.4纳秒。

使用 b.ResetTimer

有些时候我们的基础测试函数逻辑有点复杂或者在准备测试数据,如下

func BenchmarkComare(b *testing.B) {
    //准备数据
    ...
    b.ResetTimer()
	for i := 0; i < b.N; i++ {
		Compare([]byte("abcdefgh"), []byte("abcdefgh"))
	}
}

我们可以使用 b.ResetTimer() 将准备数据的时间排除在总统计时间之外。