介绍
Go 1.7,testing 包在 T 和 B 类型上引入了一个 Run 方法,允许创建子测试和子基准测试。子测试和子基准测试的引入可以更好地处理故障(failures),细化控制从命令行运行的测试,并行控制,并且经常会使代码更简单、更易于维护。
Table-driven 测试
在详细介绍之前,首先讨论在 Go 中编写测试的常用方法。 一系列相关验证可以通过循环遍历一系列测试用例来实现:
func TestTime(t *testing.T) { testCases := []struct { gmt string loc string want string }{ {"12:31", "Europe/Zuri", "13:31"}, // incorrect location name {"12:31", "America/New_York", "7:31"}, // should be 07:31 {"08:08", "Australia/Sydney", "18:08"}, } for _, tc := range testCases { loc, err := time.LoadLocation(tc.loc) if err != nil { t.Fatalf("could not load location %q", tc.loc) } gmt, _ := time.Parse("15:04", tc.gmt) if got := gmt.In(loc).Format("15:04"); got != tc.want { t.Errorf("In(%s, %s) = %s; want %s", tc.gmt, tc.loc, got, tc.want) } } } |
通常称为 table-driven(表格驱动) 测试,相比每次测试重复相同代码,减少了重复代码的数量,并且可以直接添加更多的测试用例。
Table-driven 基准测试
在 Go 1.7 之前,不可能使用相同的 table-driven 方法进行基准测试。 基准测试对整个函数的性能进行测试,因此迭代基准测试只是将它们整体作为一个基准测试。
常见的解决方法是定义单独的顶级基准,每个基准用不同的参数调用共同的函数。 例如,在 1.7 之前,strconv 包的 AppendFloat 的基准测试看起来像这样:
func benchmarkAppendFloat(b *testing.B, f float64, fmt byte, prec, bitSize int) { dst := make([]byte, 30) b.ResetTimer() // Overkill here, but for illustrative purposes. for i := 0; i < b.N; i++ { AppendFloat(dst[:0], f, fmt, prec, bitSize) } } func BenchmarkAppendFloatDecimal(b *testing.B) { benchmarkAppendFloat(b, 33909, 'g', -1, 64) } func BenchmarkAppendFloat(b *testing.B) { benchmarkAppendFloat(b, 339.7784, 'g', -1, 64) } func BenchmarkAppendFloatExp(b *testing.B) { benchmarkAppendFloat(b, -5.09e75, 'g', -1, 64) } func BenchmarkAppendFloatNegExp(b *testing.B) { benchmarkAppendFloat(b, -5.11e-95, 'g', -1, 64) } func BenchmarkAppendFloatBig(b *testing.B) { benchmarkAppendFloat(b, 123456789123456789123456789, 'g', -1, 64) } ... |
使用 Go 1.7 中提供的 Run 方法,现在将同一组基准表示为单个顶级基准:
func BenchmarkAppendFloat(b *testing.B) { benchmarks := []struct{ name string float float64 fmt byte prec int bitSize int }{ {"Decimal", 33909, 'g', -1, 64}, {"Float", 339.7784, 'g', -1, 64}, {"Exp", -5.09e75, 'g', -1, 64}, {"NegExp", -5.11e-95, 'g', -1, 64}, {"Big", 123456789123456789123456789, 'g', -1, 64}, ... } dst := make([]byte, 30) for _, bm := range benchmarks { b.Run(bm.name, func(b *testing.B) { for i := 0; i < b.N; i++ { AppendFloat(dst[:0], bm.float, bm.fmt, bm.prec, bm.bitSize) } }) } } |
每次调用 Run 方法创建一个单独的基准测试。调用 Run 方法的基准函数只运行一次,不进行性能度量。
新代码行数更多,但是更可维护,更易读,并且与通常用于测试的 table-driven 方法一致。 此外,共同的 setup 代码现在在 Run 之间共享,而不需要重置定时器。