testing 包是 go 中提供自动化测试的包,和命令 go test 配合使用,能够自动执行匹配到的函数。

TestXxx

测试函数一般是这样的:

func TestXxx(*testing.T)

测试函数需要满足一定的条件才能被执行,就像上面的那样,以Test开头,然后接一个以大写字母开头的单词,函数参数是*testing.T

测试函数所在的文件也需要满足一定的条件:文件名需要以_test.go结尾,这样的文件在go build的时候不会包含,但是可以在go test的时候调用到

BenchmarkXxx

其实还有一种测试函数:

func BenchmarkXxx(*testing.B)

和上面那个TestXxx差不多,以Benchmark开头,并接一个大写字母开头的单词,函数参数是*testing.B

这样的测试函数是压力测试函数,可以使用go test并且加上-bench参数的时候,被调用到

测试用例:

func BenchmarkHello(b *testing.B) {
    for i := 0; i < b.N; i++ {
        fmt.Sprintf("hello")
    }
}

压力测试函数必须运行 b.N 次目标代码,在压力测试函数运行期间,b.N 会动态的调整,直到基准测试功能持续足够长时间以可靠地计时为止

压力测试函数的输出类似于:

BenchmarkHello    10000000    282 ns/op

这个的意思是压力测试函数以平均 282ns 每次的速度运行了 10000000 次

如果压力测试函数需要 setup 一些操作,那么需要调用一下b.ResetTimer(),示例:

func BenchmarkBigLen(b *testing.B) {
    big := NewBig()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        big.Len()
    }
}

如果压力测试需要测试并发,那么需要使用到RunParallel函数,示例:

func BenchmarkTemplateParallel(b *testing.B) {
    templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    b.RunParallel(func(pb *testing.PB) {
        var buf bytes.Buffer
        for pb.Next() {
            buf.Reset()
            templ.Execute(&buf, "World")
        }
    })
}

ExampleXxx

测试函数以Example开头,接一个大写字母开头的单词,没有函数参数;然后将函数内部以// Output:开头下面的注释和标准输出进行比较(忽略前后的空格)。

示例:

func ExampleSalutations() {
    fmt.Println("hello, and")
    fmt.Println("goodbye")
    // Output:
    // hello, and
    // goodbye
}

有的时候输出是无需的,比如并发的时候,这个时候就需要使用// Unordered output:了:

func ExamplePerm() {
    for _, value := range Perm(4) {
        fmt.Println(value)
    }
    // Unordered output: 4
    // 2
    // 1
    // 3
    // 0
}

使用 Example 的时候有一些函数命名约定:函数 F,类型 T,类型 T 上面定义的方法 M

func Example() { ... }
func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }

如果一个函数需要有多个 Example 函数,可以以下划线作为分割添加后缀

func Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }

子测试和子压力测试

*testing.T*testing.B的 Run 方法允许定义子测试和子压力测试,而不需要定义两个测试

示例:

func TestFoo(t *testing.T) {
    // <setup code>
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) { ... })
    // <tear-down code>
}

子测试的名字需要唯一,并且和主测试的名字以/连接

可以使用-run或者-bench参数为go test指定需要运行的测试代码

go test -run ''      # Run all tests.
go test -run Foo     # Run top-level tests matching "Foo", such as "TestFooBar".
go test -run Foo/A=  # For top-level tests matching "Foo", run subtests matching "A=".
go test -run /A=1    # For all top-level tests, run subtests matching "A=1".

主测试

在有些测试中,需要在所有的测试之前做一些 setup,在所有的测试之后做一些 teardown,所以需要一个主测试来控制这些:

func TestMain(m *testing.M)

然后测试代码就不会直接运行了,而是会调用TestMain

TestMain 会在主 goroutine 中运行,并做一些 setup 和 teardown,主测试需要调用os.Exit(m.Run())

给一个例子吧: example_test.go

package example

import (
	"testing"
	"os"
)

var s string

func TestA(t *testing.T) {
	t.Logf("%s", s)
}

func TestMain(m *testing.M) {
	s = "1"
	os.Exit(m.Run())
}

func TestB(t *testing.T) {
	t.Logf("%s", s)
}

go test -v $(go list ./...) 输出:

=== RUN   TestA
--- PASS: TestA (0.00s)
	a_test.go:11: 1
=== RUN   TestB
--- PASS: TestB (0.00s)
	a_test.go:20: 1
PASS

可以看到TestMain初始化了变量 s,然后函数TestMain上面和下面的函数获取到的都是字符串 1

参考