go test测试工具

Go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的,并不需要学习新的语法、规则或工具。
go test命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内,所有以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。
*_test.go文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。

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

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

测试函数

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

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

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

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

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

  1. func (c *T) Error(args ...interface{})
  2. func (c *T) Errorf(format string, args ...interface{})
  3. func (c *T) Fail()
  4. func (c *T) FailNow()
  5. func (c *T) Failed() bool
  6. func (c *T) Fatal(args ...interface{})
  7. func (c *T) Fatalf(format string, args ...interface{})
  8. func (c *T) Log(args ...interface{})
  9. func (c *T) Logf(format string, args ...interface{})
  10. func (c *T) Name() string
  11. func (t *T) Parallel()
  12. func (t *T) Run(name string, f func(t *T)) bool
  13. func (c *T) Skip(args ...interface{})
  14. func (c *T) SkipNow()
  15. func (c *T) Skipf(format string, args ...interface{})
  16. func (c *T) Skipped() bool

例子:
splitString.go

  1. package splitString
  2. import "strings"
  3. // SplitStr ..
  4. func SplitStr(s, sep string) (res []string) {
  5. // 取索引
  6. index := strings.Index(s, sep)
  7. for index >= 0 {
  8. res = append(res, s[:index])
  9. s = s[index+1:]
  10. index = strings.Index(s, sep)
  11. }
  12. res = append(res, s)
  13. return
  14. }

测试用例:
splitStr_test.go

  1. package splitString
  2. import (
  3. "reflect"
  4. "testing"
  5. )
  6. func TestSplitStr(t *testing.T){
  7. // 实际输出结果
  8. got := SplitStr("a:b:c",":")
  9. // 期望输出
  10. want := []string{"a", "b", "c"}
  11. // 两个做比较
  12. if !reflect.DeepEqual(got,want){
  13. t.Errorf("测试失败,期望:%v,实际:%v",want,got)
  14. }
  15. }

运行测试:

  1. PS E:\DEV\Go\src\code.rookieops.com\day05\splitStr> go test
  2. PASS
  3. ok code.rookieops.com/day05/splitStr 1.362s

测试组

所谓的测试组就是将上面一个一个单独的测试函数封装到一个函数中。如下:

  1. package splitStr
  2. import (
  3. "reflect"
  4. "testing"
  5. )
  6. func TestSplitStr(t *testing.T) {
  7. // 定义一个切片,字段为我们需要测试的字段
  8. type test struct {
  9. str string
  10. sep string
  11. want []string
  12. }
  13. // 实例化结构体,将我们需要测试的数据实例化
  14. tests := []test{
  15. {"a:b:c", ":", []string{"a", "b", "c"}},
  16. {"abcdbcfwqa", "b", []string{"a", "cd", "cfwqa"}},
  17. {"啊你好帅啊好帅", "啊", []string{"你好帅", "好帅"}},
  18. }
  19. // 循环进行测试
  20. for _, ts := range tests {
  21. got := SplitStr(ts.str, ts.sep)
  22. if !reflect.DeepEqual(got, ts.want) {
  23. t.Errorf("测试失败,期望:%#v,实际:%#v", ts.want, got)
  24. }
  25. }
  26. }

推荐使用%#v的格式化方式。

子测试

如果测试用例比较多,需要定位是哪一个测试用例或者只执行某一个测试用例,需要使用子测试。

但是在使用子测试之前,可能会使用如下方法:

  1. package splitStr
  2. import (
  3. "reflect"
  4. "testing"
  5. )
  6. func TestSplitStr(t *testing.T) {
  7. // 定义一个切片,字段为我们需要测试的字段
  8. type test struct {
  9. str string
  10. sep string
  11. want []string
  12. }
  13. // 测试用例用map存储
  14. tests := map[string]test{
  15. "case1": {"a:b:c", ":", []string{"a", "b", "c"}},
  16. "case2": {"abcdbcfwqa", "b", []string{"a", "cd", "cfwqa"}},
  17. "case3": {"啊你好帅啊好帅", "啊", []string{"你好帅", "好帅"}},
  18. }
  19. // 循环进行测试
  20. for name, ts := range tests {
  21. got := SplitStr(ts.str, ts.sep)
  22. if !reflect.DeepEqual(got, ts.want) {
  23. t.Errorf("测试失败,name: %s 期望:%#v,实际:%#v", name, ts.want, got)
  24. }
  25. }
  26. }

然后执行结果如下:

  1. PS E:\DEV\Go\src\code.rookieops.com\day05\splitStr> go test -v
  2. === RUN TestSplitStr
  3. --- FAIL: TestSplitStr (0.00s)
  4. splitStr_test.go:26: 测试失败,name: case3 期望:[]string{"你好帅", "好帅"},实际:[]string{"", "你好帅", "好帅"}
  5. FAIL
  6. exit status 1
  7. FAIL code.rookieops.com/day05/splitStr 3.302s

这样可以解决我们的诉求。

但是子测试更加优雅并且可以只执行某个用例,子测试用t.Run()
代码如下:

  1. package splitStr
  2. import (
  3. "reflect"
  4. "testing"
  5. )
  6. func TestSplitStr(t *testing.T) {
  7. // 定义一个切片,字段为我们需要测试的字段
  8. type test struct {
  9. str string
  10. sep string
  11. want []string
  12. }
  13. // 测试用例用map存储
  14. tests := map[string]test{
  15. "case1": {"a:b:c", ":", []string{"a", "b", "c"}},
  16. "case2": {"abcdbcfwqa", "b", []string{"a", "cd", "cfwqa"}},
  17. "case3": {"啊你好帅啊好帅", "啊", []string{"", "你好帅", "好帅"}},
  18. }
  19. // 循环进行测试
  20. for name, ts := range tests {
  21. t.Run(name, func(t *testing.T) {
  22. got := SplitStr(ts.str, ts.sep)
  23. if !reflect.DeepEqual(got, ts.want) {
  24. t.Errorf("测试失败,name: %s 期望:%#v,实际:%#v", name, ts.want, got)
  25. }
  26. })
  27. }
  28. }

执行代码如下:

  1. PS E:\DEV\Go\src\code.rookieops.com\day05\splitStr> go test -v
  2. === RUN TestSplitStr
  3. === RUN TestSplitStr/case1
  4. === RUN TestSplitStr/case2
  5. === RUN TestSplitStr/case3
  6. --- PASS: TestSplitStr (0.00s)
  7. --- PASS: TestSplitStr/case1 (0.00s)
  8. --- PASS: TestSplitStr/case2 (0.00s)
  9. --- PASS: TestSplitStr/case3 (0.00s)
  10. PASS
  11. ok code.rookieops.com/day05/splitStr 1.400s

如果我们要运行某个测试用例,用go test -v -run=TestSplitStr/case3,如下:

  1. go test -v -run=TestSplitStr/case3
  2. === RUN TestSplitStr
  3. === RUN TestSplitStr/case3
  4. --- PASS: TestSplitStr (0.00s)
  5. --- PASS: TestSplitStr/case3 (0.00s)
  6. PASS
  7. ok code.rookieops.com/day05/splitStr 1.039s

测试覆盖率

测试覆盖率是你的代码被测试套件覆盖的百分比。通常我们使用的都是语句的覆盖率,也就是在测试中至少被运行一次的代码占总代码的比例。
Go提供内置功能来检查你的代码覆盖率。我们可以使用go test -cover来查看测试覆盖率。例如:

  1. go test -cover
  2. PASS
  3. coverage: 100.0% of statements
  4. ok code.rookieops.com/day05/splitStr 1.151s

Go还提供了一个额外的-coverprofile参数,用来将覆盖率相关的记录信息输出到一个文件。例如:
go test -coverprofile c.out

  1. go test -coverprofile c.out
  2. PASS
  3. coverage: 100.0% of statements
  4. ok code.rookieops.com/day05/splitStr 1.183s

这个文件还可以用html查看,如下:

  1. go tool cover -html c.out

image.png