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