欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

轻松入门 Go 语言:如何执行单元测试与基准测试指南

最编程 2024-02-29 10:51:51
...

 目录

1. 单元测试

1.1. go test工具

go test的参数解读:

1.2. 测试函数

1.2.1. 测试函数的格式

1.2.2. 测试函数示例

1.3. 测试组

1.4. 子测试 t.Run

1.5. 测试覆盖率 go test -cover

1.6. 基准测试--Benchmark

1.6.1. 基准测试函数格式

1.6.2. 基准测试示例

1.6.3. 性能比较函数

1.6.4. 重置时间 ResetTimer()

1.6.5. 并行测试 RunParallel

1.6.6. 结合 pprof 性能监控

1.7. Setup与TearDown

1.7.1. TestMain

1.7.2. 子测试的Setup与Teardown

1.8. 示例函数 Example

1.8.1. 示例函数的格式

1.8.2. 示例函数示例


1. 单元测试

不写测试的开发不是好程序员。我个人非常崇尚TDD(Test Driven Development)的,然而可惜的是国内的程序员都不太关注测试这一部分。 这篇文章主要介绍下在Go语言中如何做单元测试和基准测试。

1.1. go test工具

Go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的,并不需要学习新的语法、规则或工具。

    • go test命令是一个按照一定约定和组织的测试代码的驱动程序。
    • 在包目录内,所有以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。

    *_test.go文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。

    类型 格式 作用
    测试函数 函数名前缀为Test 测试程序的一些逻辑行为是否正确
    基准函数 函数名前缀为Benchmark 测试函数的性能
    示例函数 函数名前缀为Example 为文档提供示例文档

    go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件。

    Golang单元测试对文件名和方法名,参数都有很严格的要求。

    1、文件名必须以xx_test.go命名
        2、方法必须是Test[^a-z]开头
        3、方法参数必须 t *testing.T
        4、使用go test执行单元测试

    image.gif

    go test的参数解读:

    go test是go语言自带的测试工具,其中包含的是两类,单元测试和性能测试

    通过go help test可以看到go test的使用说明:

    格式形如: go test [-c] [-i] [build flags] [packages] [flags for test binary]

    参数解读:

    -c : 编译go test成为可执行的二进制文件,但是不运行测试。

    -i : 安装测试包依赖的package,但是不运行测试。

    关于build flags,调用go help build,这些是编译运行过程中需要使用到的参数,一般设置为空

    关于packages,调用go help packages,这些是关于包的管理,一般设置为空

    关于flags for test binary,调用,这些是go test过程中经常使用到的参数

      1. -test.v : 是否输出全部的单元测试用例(不管成功或者失败),默认没有加上,所以只输出失败的单元测试用例。
      2. -test.run pattern: 只跑哪些单元测试用例
      3. -test.bench patten: 只跑那些性能测试用例
      4. -test.benchmem : 是否在性能测试的时候输出内存情况
      5. -test.benchtime t : 性能测试运行的时间,默认是1s
      6. -test.cpuprofile cpu.out : 是否输出cpu性能分析文件
      7. -test.memprofile mem.out : 是否输出内存性能分析文件
      8. -test.blockprofile block.out : 是否输出内部goroutine阻塞的性能分析文件
      9. -test.memprofilerate n : 内存性能分析的时候有一个分配了多少的时候才打点记录的问题。这个参数就是设置打点的内存分配间隔,也就是profile中一个sample代表的内存大小。默认是设置为512 * 1024的。如果你将它设置为1,则每分配一个内存块就会在profile中有个打点,那么生成的profile的sample就会非常多。如果你设置为0,那就是不做打点了。你可以通过设置memprofilerate=1和GOGC=off来关闭内存回收,并且对每个内存块的分配进行观察。
      10. -test.blockprofilerate n: 基本同上,控制的是goroutine阻塞时候打点的纳秒数。默认不设置就相当于-test.blockprofilerate=1,每一纳秒都打点记录一下
      11. -test.parallel n : 性能测试的程序并行cpu数,默认等于GOMAXPROCS。
      12. -test.timeout t : 如果测试用例运行时间超过t,则抛出panic
      13. -test.cpu 1,2,4 : 程序运行在哪些CPU上面,使用二进制的1所在位代表,和nginx的nginx_worker_cpu_affinity是一个道理
      14. -test.short : 将那些运行时间较长的测试用例运行时间缩短

      目录结构:

      test
            |
             —— calc.go
            |
             —— calc_test.go

      image.gif

      1.2. 测试函数

      1.2.1. 测试函数的格式

      每个测试函数必须导入testing包,测试函数的基本格式(签名)如下:

      func TestName(t *testing.T){
          // ...
      }

      image.gif

      测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头,举几个例子:\

      func TestAdd(t *testing.T){ ... }
      func TestSum(t *testing.T){ ... }
      func TestLog(t *testing.T){ ... }

      image.gif

      其中参数t用于报告测试失败和附加的日志信息。 testing.T的拥有的方法如下:

      image.gif

      func (c *T) Error(args ...interface{})
      func (c *T) Errorf(format string, args ...interface{})
      func (c *T) Fail()
      func (c *T) FailNow()
      func (c *T) Failed() bool
      func (c *T) Fatal(args ...interface{})
      func (c *T) Fatalf(format string, args ...interface{})
      func (c *T) Log(args ...interface{})
      func (c *T) Logf(format string, args ...interface{})
      func (c *T) Name() string
      func (t *T) Parallel()
      func (t *T) Run(name string, f func(t *T)) bool
      func (c *T) Skip(args ...interface{})
      func (c *T) SkipNow()
      func (c *T) Skipf(format string, args ...interface{})
      func (c *T) Skipped() bool

      image.gif

      1.2.2. 测试函数示例

      就像细胞是构成我们身体的基本单位,一个软件程序也是由很多单元组件构成的。单元组件可以是函数、结构体、方法和最终用户可能依赖的任意东西。总之我们需要确保这些组件是能够正常运行的。

      单元测试是一些利用各种方法测试单元组件的程序,它会将结果与预期输出进行比较。

      接下来,我们定义一个split的包,包中定义了一个Split函数,具体实现如下:

      // split/split.go
      package split
      import "strings"
      // split package with a single split function.
      // Split slices s into all substrings separated by sep and
      // returns a slice of the substrings between those separators.
      func Split(s, sep string) (result []string) {
          i := strings.Index(s, sep)
          for i > -1 {
              result = append(result, s[:i])
              s = s[i+1:]
              i = strings.Index(s, sep)
          }
          result = append(result, s)
          return
      }

      image.gif

      在当前目录下,我们创建一个split_test.go的测试文件,并定义一个测试函数如下:

      // split/split_test.go
      package split
      import (
          "reflect"
          "testing"
      )
      func TestSplit(t *testing.T) { // 测试函数名必须以Test开头,必须接收一个*testing.T类型参数
          got := Split("a:b:c", ":")         // 程序输出的结果
          want := []string{"a", "b", "c"}    // 期望的结果
          if !reflect.DeepEqual(want, got) { // 因为slice不能比较直接,借助反射包中的方法比较
              t.Errorf("excepted:%v, got:%v", want, got) // 测试失败输出错误提示
          }
      }

      image.gif

      此时split这个包中的文件如下:

      split $ ls -l
          total 16
          -rw-r--r--  1 pprof staff  408  4 29 15:50 split.go
          -rw-r--r--  1 pprof  staff  466  4 29 16:04 split_test.go

      image.gif

      在split包路径下,执行go test命令,可以看到输出结果如下:

      split $ go test
      PASS
      ok      github.com/pprof/studygo/code_demo/test_demo/split       0.005s

      image.gif

      一个测试用例有点单薄,我们再编写一个测试使用多个字符切割字符串的例子,在split_test.go中添加如下测试函数:

      func TestMoreSplit(t *testing.T) {
          got := Split("abcd", "bc")
          want := []string{"a", "d"}
          if !reflect.DeepEqual(want, got) {
              t.Errorf("excepted:%v, got:%v", want, got)
          }
      }

      image.gif

      再次运行go test命令,输出结果如下:

      split $ go test
          --- FAIL: TestMultiSplit (0.00s)
              split_test.go:20: excepted:[a d], got:[a cd]
          FAIL
          exit status 1
      
      						

      上一篇:

      下一篇: 浅谈性能测试实战过程:从入门到挫败——性能基准与阶段解析