在go1.7之后,testing包T和B的引入了一个Run方法,用于创建subtests 和 sub-benchmarks. subtests 和 sub-benchmarks可以让开发者更好的处理测试中的失败,更好的控制运行哪个测试用例,控制并行测试 *** 作,测试代码更加简洁和可维护性更强。
table-driven tests 基础首先我们先讨论下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) } }}
测试函数必须以Test开头,Test后跟的名字也必须首字母大写。
上面的测试方式称为table-driven 测试法,可以降低重复代码。
在go1.7之前是不能够对benchmarks采用table-driven的方法的,如果要测试不同的参数就需要编写不同的benchmark函数,在go1.7之前常见的benchmarks测试代码如下:
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,bitSize) }}func BenchmarkAppendfloatDecimal(b *testing.B) { benchmarkAppendfloat(b, 33909,'g', -1, 64) }func BenchmarkAppendfloat(b *testing.B) { benchmarkAppendfloat(b, 339.7784, 64) }func BenchmarkAppendfloatExp(b *testing.B) { benchmarkAppendfloat(b, -5.09e75, 64) }func BenchmarkAppendfloatNegExp(b *testing.B) { benchmarkAppendfloat(b, -5.11e-95, 64) }func BenchmarkAppendfloatBig(b *testing.B) { benchmarkAppendfloat(b, 123456789123456789123456789, 64) }
go1.7之后,采用table-drive方法代码如下:
func BenchmarkAppendfloat(b *testing.B) { benchmarks := []struct{ name string float float64 fmt byte prec int bitSize int }{ {"Decimal", 64},{"float",{"Exp",{"NegExp",{"Big",... } 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) } }) }}
每个b.Run单独创建一个benchmark。
可以看到新的编码方式可读性和可维护行上更强。
如果想要子测试并发执行,则使用 b.RunParallel
table-driven tests using subtestsGo1.7之后引用Run方法用于创建subtests,对之前 table-driven tests 基础 中的代码重新写为:
func TestTime(t *testing.T) { testCases := []struct { gmt string loc string want string }{ {"12:31",{"12:31",{"08:08",tc := range testCases { t.Run(fmt.Sprintf("%s in %s",tc.loc),func(t *testing.T) { loc,err := time.LoadLocation(tc.loc) if err != nil { t.Fatal("Could not load location") } gmt,tc.gmt) if got := gmt.In(loc).Format("15:04"); got != tc.want { t.Errorf("got %s; want %s",tc.want) } }) }}
go1.7之前的 table-driven tests 基础 的测试代码运行结果为:
--- FAIL: TestTime (0.00s) time_test.go:62: Could not load location "Europe/Zuri"
虽然两个用例都是错误的,但是 第一个用例Fatalf 后,后面的用例也就没能进行运行。
使用Run的测试代码运行结果为:
--- FAIL: TestTime (0.00s) --- FAIL: TestTime/12:31_in_Europe/Zuri (0.00s) time_test.go:84: Could not load location --- FAIL: TestTime/12:31_in_America/New_York (0.00s) time_test.go:88: got 07:31; want 7:31
Fatal 导致subtest被跳过,不过不影响其他subtest以及父test的测试。
针对每一个子测试,go test命令都会打印出一行测试摘要。它们是分离的、独立统计的。这可以让我们进行更加精细的测试,细到每次输入输出。
过滤执行测试用例subtests和sub-benchmarks可以使用 -run or -bench flag
来对测试用例进行过滤运行。 -run or -bench flag后跟以’/’分割的正则表达式,用来制定特定的测试用例。
$ go test -run=TestTime/"in Europe"--- FAIL: TestTime (0.00s) --- FAIL: TestTime/12:31_in_Europe/Zuri (0.00s) time_test.go:85: Could not load location执行TestTime下匹配”12:[0-9] ” 的子测试
$ go test -run=Time/12:[0-9] -v=== RUN TestTime=== RUN TestTime/12:31_in_Europe/Zuri=== RUN TestTime/12:31_in_America/New_York--- FAIL: TestTime (0.00s) --- FAIL: TestTime/12:31_in_Europe/Zuri (0.00s) time_test.go:85: Could not load location --- FAIL: TestTime/12:31_in_America/New_York (0.00s) time_test.go:89: got 07:31; want 7:31
$ go test -run=Time//New_York--- FAIL: TestTime (0.00s) --- FAIL: TestTime/12:31_in_America/New_York (0.00s) time_test.go:88: got 07:31; want 7:31func (*T) Parallel
func (t *T) Parallel()
使用t.Parallel(),使测试和其它子测试并发执行。
tc := tc这个地方很关键,不然多个子测试可能使用的tc是同一个。
func TestGroupedParallel(t *testing.T) { for _,tc := range testCases { tc := tc // capture range variable t.Run(tc.name,func(t *testing.T) { t.Parallel() if got := foo(tc.in); got != tc.out { t.Errorf("got %v; want %v",tc.out) } ... }) }}func (*B) RunParallel
func (b *B) RunParallel(body func(*PB))
RunParallel runs a benchmark in parallel. It creates multiple goroutines and distributes b.N iterations among them. The number of goroutines defaults to GOMAXPROCS. To increase parallelism for non-cpu-bound benchmarks,call SetParallelism before RunParallel. RunParallel is usually used with the go test -cpu flag.
The body function will be run in each goroutine. It should set up any goroutine-local state and then iterate until pb.Next returns false. It should not use the StartTimer,StopTimer,or resetTimer functions,because they have global effect. It should also not call Run.
RunParallel并发的执行benchmark。RunParallel创建多个goroutine然后把b.N个迭代测试分布到这些goroutine上。goroutine的数目默认是GOMAXPROCS。如果要增加non-cpu-bound的benchmark的并个数,在执行RunParallel之前调用SetParallelism。
不要使用 StartTimer,or resetTimer functions这些函数,因为这些函数都是 global effect的。
package mainimport ( "bytes" "testing" "text/template")func main() { // Parallel benchmark for text/template.Template.Execute on a single object. testing.Benchmark(func(b *testing.B) { templ := template.Must(template.New("test").Parse("Hello,{{.}}!")) // RunParallel will create GOMAXPROCS goroutines // and distribute work among them. b.RunParallel(func(pb *testing.PB) { // Each goroutine has its own bytes.Buffer. var buf bytes.Buffer for pb.Next() { // The loop body is executed b.N times total across all goroutines. buf.reset() templ.Execute(&buf,"World") } }) })}本人测试实例 Benchmark测试代码
func BenchmarkProductInfo(b *testing.B) { // b.resetTimer() testCases := []string{"pn3","p7","p666"} for _,productID := range testCases { // b.SetParallelism b.Run(productID,func(b *testing.B) { for i := 0; i < b.N; i++ { mgoDB.ecnGetProductInfoOfProductID(productID) } }) }}func BenchmarkProductInfoParalle(b *testing.B) { // b.resetTimer() testCases := []string{"pn3",tproductID := range testCases { // b.SetParallelism productID := tproductID b.RunParallel(func(b *testing.PB) { for b.Next() { mgoDB.ecnGetProductInfoOfProductID(productID) } }) }}func BenchmarkProductLock(b *testing.B) { // b.resetTimer() testCases := []string{"pn3",func(b *testing.B) { for i := 0; i < b.N; i++ { mgoDB.CheckProductLockStatus(productID) } }) }}func BenchmarkProductLockParallel(b *testing.B) { // b.resetTimer() testCases := []string{"pn3",tproductID := range testCases { // b.SetParallelism productID := tproductID b.RunParallel(func(b *testing.PB) { for b.Next() { mgoDB.CheckProductLockStatus(productID) } }) }}执行如下测试命令
go test -bench="."
结果
BenchmarkProductInfo/pn3-4 10000 107704 ns/opBenchmarkProductInfo/p7-4 10000 108921 ns/opBenchmarkProductInfo/p666-4 10000 107163 ns/opBenchmarkProductInfoParalle-4 10000 113386 ns/opBenchmarkProductLock/pn3-4 10000 100418 ns/opBenchmarkProductLock/p7-4 20000 97373 ns/opBenchmarkProductLock/p666-4 20000 96905 ns/opBenchmarkProductLockParallel-4 10000 108399 ns/op执行如下测试命令
go test -bench=ProductInfo
过滤测试函数名中包含ProductInfo的测试用例,结果:
BenchmarkProductInfo/pn3-4 10000 111065 ns/opBenchmarkProductInfo/p7-4 10000 118515 ns/opBenchmarkProductInfo/p666-4 10000 111723 ns/opBenchmarkProductInfoParalle-4 10000 118641 ns/op执行如下测试命令
go test -bench=oductInfo
过滤测试函数名中包含oductInfo的测试用例,结果:
BenchmarkProductInfo/pn3-4 10000 107338 ns/opBenchmarkProductInfo/p7-4 10000 109848 ns/opBenchmarkProductInfo/p666-4 10000 109344 ns/opBenchmarkProductInfoParalle-4 10000 114351 ns/op执行如下测试命令
go test -bench=ProductInfo/p7
过滤测试函数名中包含ProductInfo且子测试名称包含p7的测试用例,同时我们可以注意到并行的测试也执行了。结果:
BenchmarkProductInfo/p7-4 10000 109045 ns/opBenchmarkProductInfoParalle-4 10000 117569 ns/opTest测试代码
func TestCheckProductLockt(t *testing.T) { testCases := []string{"a1","a2","a3"} for _,productID := range testCases { t.Log(productID) t.Run(productID,func(t *testing.T) { _,ret := mgoDB.ecnGetProductInfoOfProductID(productID) if ret != Success { t.Fatalf("faIEld") } }) }}func TestCheckProductLocktParalle(t *testing.T) { testCases := []string{"a1",tproductID := range testCases { productID := tproductID t.Log(productID) t.Run(productID,func(t *testing.T) { t.Parallel() _,ret := mgoDB.ecnGetProductInfoOfProductID(productID) if ret != Success { t.Fatalf("faIEld") } }) }}func TestUserIDMatchRole(t *testing.T) { reqData := []struct { ProductID string UserID string RoleType string }{ {"pn2","48176d26e860975e96518b80a3520407","HR"},{"pn2","CEO"},"CTO"},} for _,data := range reqData { // t.Log(data) t.Run(fmt.Sprint("%s %s",data.ProductID,data.RoleType),func(t *testing.T) { if ret := checkUserMatchProductRole(data.ProductID,data.UserID,data.RoleType); ret != Success { t.Error("not match") } }) }}func TestUserIDMatchRoleParall(t *testing.T) { reqData := []struct { ProductID string UserID string RoleType string }{ {"pn2",tdata := range reqData { // data := tdata //重要 t.Log(data) t.Run(fmt.Sprint("%s %s",func(t *testing.T) { t.Parallel() if ret := checkUserMatchProductRole(data.ProductID,data.RoleType); ret != Success { t.Error("not match") } }) }}执行如下测试命令
go test -bench="."
结果
--- FAIL: TestCheckProductLockt (0.00s) ecn_test.go:626: a1 --- FAIL: TestCheckProductLockt/a1 (0.00s) ecn_test.go:630: faIEld ecn_test.go:626: a2 --- FAIL: TestCheckProductLockt/a2 (0.00s) ecn_test.go:630: faIEld ecn_test.go:626: a3 --- FAIL: TestCheckProductLockt/a3 (0.00s) ecn_test.go:630: faIEld--- FAIL: TestCheckProductLocktParalle (0.00s) ecn_test.go:642: a1 ecn_test.go:642: a2 ecn_test.go:642: a3 --- FAIL: TestCheckProductLocktParalle/a1 (0.00s) ecn_test.go:647: faIEld --- FAIL: TestCheckProductLocktParalle/a2 (0.00s) ecn_test.go:647: faIEld --- FAIL: TestCheckProductLocktParalle/a3 (0.00s) ecn_test.go:647: faIEld--- FAIL: TestUserIDMatchRole (0.00s) ecn_test.go:668: {pn2 48176d26e860975e96518b80a3520407 HR} --- FAIL: TestUserIDMatchRole/%s_%spn2HR (0.00s) ecn_test.go:671: not match ecn_test.go:668: {pn2 48176d26e860975e96518b80a3520407 CEO} --- FAIL: TestUserIDMatchRole/%s_%spn2CEO (0.00s) ecn_test.go:671: not match ecn_test.go:668: {pn2 48176d26e860975e96518b80a3520407 CTO} --- FAIL: TestUserIDMatchRole/%s_%spn2CTO (0.00s) ecn_test.go:671: not match--- FAIL: TestUserIDMatchRoleParall (0.00s) ecn_test.go:692: {pn2 48176d26e860975e96518b80a3520407 HR} ecn_test.go:692: {pn2 48176d26e860975e96518b80a3520407 CEO} ecn_test.go:692: {pn2 48176d26e860975e96518b80a3520407 CTO} --- FAIL: TestUserIDMatchRoleParall/%s_%spn2HR (0.00s) ecn_test.go:696: not match --- FAIL: TestUserIDMatchRoleParall/%s_%spn2CTO (0.00s) ecn_test.go:696: not match --- FAIL: TestUserIDMatchRoleParall/%s_%spn2CEO (0.00s) ecn_test.go:696: not match
在测试代码中我们添加了t.log的打印,通过打印对比并发版本和非并发版本的输出,可以看到非并发版本的测试的确时顺序执行的,而并发版本的测试是并发执行的。
参考网址Using Subtests and Sub-benchmarks
解读2016之Golang篇:极速提升,逐步超越
以上是内存溢出为你收集整理的golang 1.7之后高级测试方法之子测试,子基准测试(subtest sub-benchmarks)全部内容,希望文章能够帮你解决golang 1.7之后高级测试方法之子测试,子基准测试(subtest sub-benchmarks)所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)