Go语言测试入门(二)

基准测试

基准测试需要以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我们可以查看它检查的列表:

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇