大家好,我是李二狗!一起坚持!一起学习!
每日一课,无论长短,有所学有所得
业精于勤技在专,行则将至事必成

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

国外的很多公司很多优秀的程序员都比较重视TDD,但是在国内十分少见;(TDD:测试驱动开发(test driven devlopment))

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

测试文件

_test.gogo testgo build

测试函数

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

TestBenchmarkExample

测试命令

go test
go test*_test.go测试函数

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


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

单元测试函数

// 文件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
}
// 文件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() {

}
=== 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 -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
// 修改以上示例代码中的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
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){}
func BenchmarkSplit(b *testing.B) {
    // 注意b.N
   for i := 0; i < b.N; i++ {
       Split("沙河有沙又有河", "沙")
   }
}
// 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
// `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
package fib

// Fib 是一个计算第n个斐波那契数的函数
func Fib(n int) int {
   if n < 2 {
       return n
   }
   return Fib(n-1) + Fib(n-2)
}
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) }
// 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

示例函数

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