基准测试
基准测试需要以Benchmark开头,参数需要使用 *testing.B。在执行go test的时候需要加上 -bench参数。
func BenchmarkXxx(*testing.B) |
基准测试函数示例:
func BenchmarkRandInt(b *testing.B) { for i := 0; i < b.N; i++ { rand.Int() } } |
其中b.N表示基准测试的方法会运行b.N次。在执行期间,go会调整b.N持续足够长的时间。
输出结果为:
BenchmarkRandInt-8 68453040 17.8 ns/op |
表示以每次循环17.8 ns的速度运行了68453040次。
如果在运行基准测试前需要一些耗时的配置,则可以在执行前先重置定时器,代码如下:
func BenchmarkBigLen(b *testing.B) { big := NewBig() b.ResetTimer() for i := 0; i < b.N; i++ { big.Len() } } |
如果基准测试需要在并行设置中测试性能,则可以使用RunParallel函数。
在测试时可以搭配go test -cpu参数。
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”) } }) } |
表驱动测试
在许多情况下,表驱动测试可以覆盖很多基础的情况:
- 表的每一条都是一个完整的测试用例,其中包含输入和预期输出,有时还包含其他信息,例如测试名称。
- 如果在编写测试的时候,你开始使用复制粘贴,你就可以考虑重构为表驱动测试。
go中允许在run方法中定义子单元测试和子基准测试。
这样的好处是:
- 不必为每个子测试单独定义函数,新增测试用例只需要新增一条case即可。
- 测试的结构更加清晰,可读性更好,可以直观的看到每个参数和期望返回值。
- 测试报告格式统一,易于阅读。
var flagtests = []struct { in string out string }{ {“%a”, “[%a]”}, {“%-a”, “[%-a]”}, {“%+a”, “[%+a]”}, {“%#a”, “[%#a]”}, {“% a”, “[% a]”}, {“%0a”, “[%0a]”}, {“%1.2a”, “[%1.2a]”}, {“%-1.2a”, “[%-1.2a]”}, {“%+1.2a”, “[%+1.2a]”}, {“%-+1.2a”, “[%+-1.2a]”}, {“%-+1.2abc”, “[%+-1.2a]bc”}, {“%-1.2abc”, “[%-1.2a]bc”}, } func TestFlagParser(t *testing.T) { var flagprinter flagPrinter for _, tt := range flagtests { t.Run(tt.in, func(t *testing.T) { s := Sprintf(tt.in, &flagprinter) if s != tt.out { t.Errorf(“got %q, want %q”, s, tt.out) } }) } } |
t.Errorf中分别输出了,实际的值和期望值。当测试失败时,即使不阅读测试代码,也可以立即看出哪个测试失败以及为什么失败。
使用t.Errorf,即使出错,测试也仍将继续。
并行测试
为了提升测试性能可以使用并行测试函数t.Parallel()。
package main import ( “testing” ) func TestTLog(t *testing.T) { t.Parallel() //标记的TLog作为能够与其他测试并行运行的 tests := []struct { name string }{ {“test 1”}, {“test 2”}, {“test 3”}, {“test 4”}, } for _, tt := range tests { tt := tt // NOTE: https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables t.Run(tt.name, func(t *testing.T) { t.Parallel() // 标记每个测试用例作为能够彼此并行运行的 t.Log(tt.name) }) } } |
在这之上它还提供了额外的设置(setup)或拆卸(teardown)代码的方法:
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 的名称的组合,以斜杠分隔,最后结尾可以增加序列号。
-run 和 -bench 命令行标志的参数是与测试名称相匹配的非固定的正则表达式。对于具有多个斜杠分隔元素(例如子测试)的测试,该参数本身是斜杠分隔的,其中表达式依次匹配每个名称元素。因为它是非固定的,一个空的表达式匹配任何字符串。
go test -run ” # Run 所有测试。 go test -run Foo # Run 匹配 “Foo” 的顶层测试,例如 “TestFooBar”。 go test -run Foo/A= # 匹配顶层测试 “Foo”,运行其匹配 “A=” 的子测试。 go test -run /A=1 # 运行所有匹配 “A=1” 的子测试。 |
子测试也可用于控制并行性。所有的子测试完成后,父测试才会完成。在这个例子中,所有的测试是相互并行运行的,当然也只是彼此之间,不包括定义在其他顶层测试的子测试:
func TestGroupedParallel(t *testing.T) { for _, tc := range tests { tc := tc // capture range variable t.Run(tc.Name, func(t *testing.T) { t.Parallel() … }) } } |
在并行子测试完成之前,Run 方法不会返回,这提供了一种测试后清理的方法:
func TestTeardownParallel(t *testing.T) { // This Run will not return until the parallel tests finish. t.Run(“group”, func(t *testing.T) { t.Run(“Test1”, parallelTest1) t.Run(“Test2”, parallelTest2) t.Run(“Test3”, parallelTest3) }) // <tear-down code> } |
覆盖率测试
执行工具可以报告测试覆盖率统计信息。
$ go test -cover PASS coverage: 96.4% of statements ok strings 0.692s |
go tool可以生成由cover tool解释的coverage profiles文件。
$ go test -coverprofile=cover.out $ go tool cover -func=cover.out strings/reader.go: Len 66.7% strings/strings.go: TrimSuffix 100.0% … many lines omitted … strings/strings.go: Replace 100.0% strings/strings.go: EqualFold 100.0% total: (statements) 96.4% |
覆盖可视化
go tool cover -html=cover.out |
数据竞争检测
数据竞争是隐秘且难以捕捉的编程错误之一,他们会导致系统的不稳定和莫名的故障,通常是在生产中运行一段时间后才会发现。所以Go在1.1版本中包含了竞争检测器。
当两个goroutine并发访问同一变量并且至少其中之一是写操作时,就会发生数据争用。
竞争检测器基于C/C++ThreadSanitizer运行时库,该库已经用于检测Google内部代码库和Chromium中的许多错误。
竞争检测器和Go工具完全集成在一起,要启用竞争检测只需将-race标志传递给go tool以启用数据竞争检测。
$ go test -race mypkg // to test the package $ go run -race mysrc.go // to run the source file $ go build -race mycmd // to build the command $ go install -race mypkg // to install the package |
一个简单的例子,
package main import “fmt” func main() { i := 0 go func() { i++ // write i }() fmt.Println(i) // read i } |
运行go run -race来进行检测。如果有竞争就会输出warning,提示代码存在1处数据竞争,说明了数据会在第9行写,并且同时会在12行读形成了数据竞争。
go run -race race.go 0 ================== WARNING: DATA RACE Write at 0x00c00009c008 by goroutine 6: main.main.func1() ~/race.go:9 +0x4e Previous read at 0x00c00009c008 by main goroutine: main.main() ~/race.go:12 +0x88 Goroutine 6 (running) created at: main.main() ~/race.go:8 +0x7a ================== Found 1 data race(s) exit status 66 |
使用静态分析查找错误:vet
go 的 vet 工具可以用来检查 go 代码中可以通过编译但仍然有可能存在错误的代码。
vet工具检查代码是否存在常见的程序员错误:
go vet [package] |
通过go tool vet help我们可以查看它检查的列表: