Go 测试
Go 测试由 go test 命令和 testing 包构成。
- 测试函数必须写成类似于
func TestXxx(*testing.T)的格式 - 测试文件可以和被测试的在同一个
package中,或者在被测试包名_test包中
如果测试文件和被测试文件在同一个包中,那么该测试函数是未被导出的
package abs
import "testing"
func TestAbs(t *testing.T) {
got := Abs(-1)
if got != 1 {
t.Errorf("Abs(-1) = %d; want 1", got)
}
}
如果使用独立的_test包,那么要测试的包必须显式导入,并且只能测试被导出的函数(黑盒测试)
package abs_test
import (
"testing"
"path_to_pkg/abs"
)
func TestAbs(t *testing.T) {
got := abs.Abs(-1)
if got != 1 {
t.Errorf("Abs(-1) = %d; want 1", got)
}
}
Go 基准测试
函数必须形如func BenchmarkXxx(*testing.B),使用go test -bench来运行基准测试
func BenchmarkRandInt(b *testing.B) {
for i := 0; i < b.N; i++ {
rand.Int()
}
}
// 这个基准测试函数会运行 b.N 次,b.N的大小会自动进行调整,让测试的时间保持在一个合理的范围内
// 假如测试结果为 “BenchmarkRandInt-8 68453040 17.8 ns/op”
// 说明了被运行了68453040次,每次消耗17.8ns
如果需要在基准测试之前做一些初始化的工作,可以使用ResetTimer来重置计时器
func BenchmarkRandInt(b *testing.B) {
... // expensive setup
b.ResetTimer()
for i := 0; i < b.N; i++ {
rand.Int()
}
}
Go 示例测试
Go 语言还能运行并验证代码,实例函数必须形如func ExampleXxx(),并且在后面加上Output注释,Output注释中的内容会被用于验证输出结果(Output 注释内容前后的空格会被忽略)
func ExampleSalutations() {
fmt.Println("hello, and")
fmt.Println("goodbye")
// Output:
// hello, and
// goodbye
}
Output注释可以添加前缀来说明输出是无序的
func ExamplePerm() {
for _, value := range Perm(5) {
fmt.Println(value)
}
// Unordered output: 4
// 2
// 1
// 3
// 0
}
示例函数如果没有Output注释,那么它只会被编译,不会被执行
以下是命名规范:
func Example() { ... } // 包的示例
func ExampleF() { ... } // 函数F的示例
func ExampleT() { ... } // 类型T的示例
func ExampleT_M() { ... } // 类型T的方法M的示例
可以使用同一个后缀来对示例函数进行分组,后缀必须小写开头
// suffix可以是package/type/function/method名
func Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }
Go 模糊测试
go test -fuzz支持模糊测试,使用随机生成的数据来测试程序的正确性,模糊测试函数必须形如func FuzzXxx(t *testing.F, args ...T)
fuzzing 的参数只支持以下几种类型:
string, []byte
int, int8, int16, int32/rune, int64
uint, uint8/byte, uint16, uint32, uint64
float32, float64
bool
基本格式如下图:
func FuzzHex(f *testing.F) {
for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} {
f.Add(seed)
}
f.Fuzz(func(t *testing.T, in []byte) { // in 是根据输入的种子随机生成的
enc := hex.EncodeToString(in)
out, err := hex.DecodeString(enc)
if err != nil {
t.Fatalf("%v: decode: %v", in, err)
}
if !bytes.Equal(in, out) {
t.Fatalf("%v: not equal after round trip: %v", in, out)
}
})
}
- 每一个 fuzz test 维护了一个语料库,可以用
f.Add向这个语料库中添加模糊测试的种子,或者将种子放在testdata/fuzz/<Name>文件里(Name 是某次模糊测试的名字)。 - fuzzing 基于种子语料库生成随机输入,种子是可选的,但是提供一些代码覆盖比较好的测试样例给 fuzzing engine 时,engine 将会更有效的发现 bug。
- 如果 fuzz 测试的时候失败了,那么 fuzzing engine 会将失败的输入保存在
testdata/fuzz/<Name>文件里,这个失败的输入将会被当作下一次 fuzzing 的种子。
跳过某一测试
测试或者基准测试可以通过调用Skip方法来跳过,示例如下:
func TestTimeConsuming(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
...
}
子测试和子基准测试
测试和基准测试允许定义子测试和子基准测试,而不必为每个测试定义单独的函数。这使得像表驱动的基准测试和创建分层测试这样的用法成为可能。它还提供了一种共享公共设置和拆卸代码的方法:
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>
}
子测试同样可以用于并发控制,只有当所有子测试都完成后,夫测试才会完成。
// 在这个例子中,每个子测试都会并发执行
func TestGroupedParallel(t *testing.T) {
for _, tc := range tests {
tc := tc // capture range variable
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
...
})
}
}
go test -run 使用方法
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".
go test -fuzz FuzzFoo # Fuzz the target matching "FuzzFoo"
Main
- 对于每个测试包,都可以定义一个名为
TestMain的函数,它具有如下签名:func TestMain(m *testing.M)。 - 所有测试都会首先调用
TestMain而不是直接运行测试或者基准测试,并且可以在调用m.Run前后进行任何 setup 和 teardown 操作。 m.Run会运行测试包中的测试并返回一个返回值,该返回值可以传递给os.Exit。
最简单的一个例子:
func TestMain(m *testing.M) {
// call flag.Parse() here if TestMain uses flags
os.Exit(m.Run())
}