关于golang:golang中的单元测试

优良的代码习惯肯定是随同着单元测试的,这也是go语言设计的哲学;

国外的很多公司很多优良的程序员都比拟器重TDD,然而在国内非常少见;(TDD:测试驱动开发(test driven devlopment))

无论如何,学习并应用golang的单元测试,不是浪费时间,而是让你的代码更加优雅健硕!

测试文件

文件名以_test.go为后缀的文件均为测试代码,都会被go test测试捕获,不会被go build编译;

测试函数

测试文件中的三种函数类型:

  • 单元测试函数:函数名前缀Test;测试程序的逻辑
  • 基准函数:函数名前缀Benchmark;测试函数的性能
  • 示例函数:函数名前缀Example;会呈现在godoc中的,为文档提供示例文档

测试命令

Go语言中的测试依赖go test命令;在此命令下增加各种不同的参数以实现不同目标的测试;前面会一一介绍;

go test命令会遍历所有的*_test.go文件中合乎上述命名规定的测试函数

而后生成一个长期的main包用于调用相应的测试函数,而后构建并运行、报告测试后果,最初清理测试中生成的临时文件;


接下来别离介绍单元测试函数、基准函数、示例函数:

单元测试函数

  • 单元测试函数的格局:

    1. func TestName(t *testing.T) {}
    2. 函数的名字必须以Test结尾,可选的后缀名必须以大写字母结尾
    3. 每个测试函数必须导入testing包;对于testing包中的办法能够去看一下源码;
    4. 参数t用于报告测试失败和附加的日志信息
  • 一个简略的测试函数示例:将输入的后果预期的后果进行比拟

    1. 创立业务函数

      // 文件split/split.go:定义一个split的包,包中定义了一个Split函数
      package split
      
      import "strings"
      
      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
      }
    2. 创立测试文件

      // 文件split/split_test.go:创立一个split_test.go的测试文件
      package split
      
      import (
          "reflect"
          "testing"
      )
      
      // 单元测试函数
      // 测试函数名必须以Test结尾,必须接管一个*testing.T类型参数
      // 1. 间接调用业务函数
      // 2. 定义冀望后果
      // 3. 比拟理论后果和冀望后果
      func TestSplit(t *testing.T) {
          got := Split("a:b:c", ":")      // 调用程序并返回程序后果
          want := []string{"a", "b", "c"} // 冀望的后果
      
          if !reflect.DeepEqual(want, got) { // 因为slice不能间接比拟,借助反射包中的办法比拟
              t.Errorf("expected:%v, got:%v", want, got) // 如果测试失败输入谬误提醒
          }
      }
      
      // 提供一个失败的单元测试
      func TestSplitFail(t *testing.T) {
          got := Split("abcd", "bc")
          want := []string{"a", "d"}
      
          if !reflect.DeepEqual(want, got) {
              t.Errorf("expected:%v, got:%v", want, got)
          }
      }
      
      // 基准测试函数
      func BenchmarkSplit(b *testing.B) {
      
      }
      
      // 示例函数
      func ExampleSplit() {
      
      }
    3. 执行测试命令

      进入split目录下,间接运行 go test 命令即可;

      如果运行go test -v的话,能够看到更具体的输入后果:晓得哪个测试函数没有通过,错在哪里

      === RUN   TestSplit
      --- PASS: TestSplit (0.00s)
      === RUN   TestSplitFail
          split_test.go:28: expected:[a d], got:[a cd]
      --- FAIL: TestSplitFail (0.00s)
      FAIL
      exit status 1
      FAIL    gotest/split    0.001s
  • 其余go test命令

    go test -run=?run对应一个正则表达式,只有函数名匹配上的测试函数才会被go test命令执行;

    // 比方以上代码执行命令:go test -v -run=Fail
    // 示意本次只运行 能正则匹配到Fail的 测试函数
    === RUN   TestSplitFail
        split_test.go:28: expected:[a d], got:[a cd]
    --- FAIL: TestSplitFail (0.00s)
    FAIL
    exit status 1
    FAIL    gotest/split    0.001s

    go test -short:跳过测试函数中蕴含testing.Short()函数的测试函数;个别用于跳过执行起来太耗时的测试函数;比方:

    // 批改以上示例代码中的TestSplitFail函数如下
    func TestSplitFail(t *testing.T) {
        if testing.Short() {
            t.Skip("short模式下会跳过该测试用例")
        }
        got := Split("abcd", "bc") // 调用程序并返回程序后果
        want := []string{"a", "d"} // 冀望的后果
    
        if !reflect.DeepEqual(want, got) { // 因为slice不能间接比拟,借助反射包中的办法比拟
            t.Errorf("expected:%v, got:%v", want, got) // 如果测试失败输入谬误提醒
        }
    }
    
    // 而后执行命令`go test -v -short`打印如下后果:
    === RUN   TestSplit
    --- PASS: TestSplit (0.00s)
    === RUN   TestSplitFail
        split_test.go:25: short模式下会跳过该测试用例
    --- SKIP: TestSplitFail (0.00s)
    PASS
    ok      gotest/split    0.002s

    go test -cover测试覆盖率:覆盖率是指测试代码笼罩的业务代码的占比;

    go test -cover -coverprofile=c.out将覆盖率相干的信息输入到以后文件夹上面的c.out文件中;

    再而后执行go tool cover -html=c.out,应用cover工具来解决生成的记录信息,该命令会关上本地的浏览器窗口生成一个HTML报告;

  • 子测试:对多组测试用例可能清晰精确的进行谬误定位

    Go1.7+中新增了子测试,咱们能够依照如下形式应用t.Run执行子测试:

    func TestSplit(t *testing.T) {
        type test struct {
            input string
            sep   string
            want  []string
        }
        // 定义多组测试用例
        tests := map[string]test{
            "simple":      {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
            "wrong sep":   {input: "a:b:c", sep: ",", want: []string{"a:b:c"}},
            "more sep":    {input: "abcd", sep: "bc", want: []string{"a", "d"}},
            "leading sep": {input: "沙河有沙又有河", sep: "沙", want: []string{"河有", "又有河"}},
        }
    
        // t.Run就是子测试
        for name, tc := range tests {
            t.Run(name, func(t *testing.T) { // 应用t.Run()执行子测试
                got := Split(tc.input, tc.sep)
                if !reflect.DeepEqual(got, tc.want) {
                    t.Errorf("expected:%#v, got:%#v", tc.want, got)
                }
            })
        }
    }
    
    // 执行 go test -v
    === RUN   TestSplit
    === RUN   TestSplit/simple
    === RUN   TestSplit/wrong_sep
    === RUN   TestSplit/more_sep
        split_test.go:34: expected:[]string{"a", "d"}, got:[]string{"a", "cd"}
    === RUN   TestSplit/leading_sep
        split_test.go:34: expected:[]string{"河有", "又有河"}, got:[]string{"", "\xb2\x99河有", "\xb2\x99又有河"}
    --- FAIL: TestSplit (0.00s)
        --- PASS: TestSplit/simple (0.00s)
        --- PASS: TestSplit/wrong_sep (0.00s)
        --- FAIL: TestSplit/more_sep (0.00s)
        --- FAIL: TestSplit/leading_sep (0.00s)
    FAIL
    exit status 1
    FAIL    gotest/split    0.002s

基准函数

在肯定的工作负载之下检测程序性能;

根本格局如:func BenchmarkName(b *testing.B){}

  • Benchmark为前缀,前面跟着首字母大写
  • 必须要有testing.B类型的参数;
  • 基准测试必须要执行b.N次,这样的测试才有对照性,b.N的值是零碎依据理论状况去调整的(见前面应用示例)
  • 对于testing.B的办法能够参考源码
  • 一个简略的基准函数的示例:

    1. 创立测试函数(持续接着下面的示例代码)

      func BenchmarkSplit(b *testing.B) {
          // 留神b.N
          for i := 0; i < b.N; i++ {
              Split("沙河有沙又有河", "沙")
          }
      }
    2. 执行测试命令-bench=$正则匹配

      // go test -bench=Split
      goos: windows
      goarch: amd64
      pkg: go-test/split
      cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
      BenchmarkSplit-4         4017925               311.9 ns/op
      PASS
      ok      go-test/split      1.976s

      以上输入了电脑的一些信息;

      其中BenchmarkSplit-4中的4示意GOMAXPROCS的值;

      4017925示意调用该函数的次数;

      311.9 ns/op调用该函数的均匀耗时;

      // `go test -bench=Split -benchmem`减少了内存调配的统计数据
      goos: windows
      goarch: amd64
      pkg: go-test/split
      cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
      BenchmarkSplit-4         3995856               300.9 ns/op           112 B/op          3 allocs/op
      PASS
      ok      go-test/split      1.890s

      其中112 B/op示意每次操作内存调配了112字节;

      3 allocs/op示意每次操作进行了3次内存调配;

  • 性能比拟函数

    1. 基准函数只能给出相对耗时;理论中咱们可能想晓得不同操作的绝对耗时;
    2. 性能比拟函数通常是一个带有参数的函数,被多个不同的Benchmark函数传入不同的值来调用;
  • 一个简略的基准测试中的性能比拟函数示例:

    1. 创立一个fib目录,并新建一个fib.go文件

      package fib
      
      // Fib 是一个计算第n个斐波那契数的函数
      func Fib(n int) int {
          if n < 2 {
              return n
          }
          return Fib(n-1) + Fib(n-2)
      }
    2. 同目录下创立一个测试函数文件fib_test.go

      package fib
      
      import (
          "testing"
      )
      
      func benchmarkFib(b *testing.B, n int) {
          for i := 0; i < b.N; i++ {
              Fib(n)
          }
      }
      
      func BenchmarkFib1(b *testing.B)  { benchmarkFib(b, 1) }
      func BenchmarkFib2(b *testing.B)  { benchmarkFib(b, 2) }
      func BenchmarkFib3(b *testing.B)  { benchmarkFib(b, 3) }
      func BenchmarkFib10(b *testing.B) { benchmarkFib(b, 10) }
      func BenchmarkFib20(b *testing.B) { benchmarkFib(b, 20) }
      func BenchmarkFib40(b *testing.B) { benchmarkFib(b, 40) }
    3. 执行测试命令

      // go test -bench=Fib
      goos: windows
      goarch: amd64
      pkg: go-test/fib
      cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
      BenchmarkFib1-4         470478618                2.393 ns/op
      BenchmarkFib2-4         178773399                6.737 ns/op
      BenchmarkFib3-4         100000000               12.60 ns/op
      BenchmarkFib10-4         3025942               421.8 ns/op
      BenchmarkFib20-4           24792             55344 ns/op
      BenchmarkFib40-4               2         724560000 ns/op
      PASS
      ok      go-interview/fib        11.675s

      BenchmarkFib40-4 2 724560000 ns/op:第一个是对应的比拟函数;第二个是执行的次数;第三个是执行的均匀工夫;

      由此可见fib函数入参值越大,函数执行的效率越低;

  • 基准测试命令除了以上这些外还有一些其余的参数做其余用途:比方这些参数benchtime,ResetTimer,RunParallel,SetParallelism,cpu,setup,teardown,TestMain,能够理解一下

示例函数

示例函数可能作为文档间接应用,例如基于web的godoc中能把示例函数与对应的函数或包相关联


保持每日输入go开发+面试题+算法+工作教训等后端相干技术

对于我往年的打算请查看:flag-2022

更多博客内容请查看bigshake


评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理