Skip to content

单元测试

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包,测试函数的基本格式(签名)如下:

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

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

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

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

go
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

例子:

splitString.go

go
package splitString

import "strings"

// SplitStr ..
func SplitStr(s, sep string) (res []string) {
	// 取索引
	index := strings.Index(s, sep)
	for index >= 0 {
		res = append(res, s[:index])
		s = s[index+1:]
		index = strings.Index(s, sep)
	}
	res = append(res, s)
	return
}

测试用例:

splitStr_test.go

go
package splitString

import (
	"reflect"
	"testing"
)

func TestSplitStr(t *testing.T){
	// 实际输出结果
	got := SplitStr("a:b:c",":")
	// 期望输出
	want := []string{"a", "b", "c"}
	// 两个做比较
	if !reflect.DeepEqual(got,want){
		t.Errorf("测试失败,期望:%v,实际:%v",want,got)
	}
}

运行测试:

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

测试组

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

go
package splitStr

import (
	"reflect"
	"testing"
)

func TestSplitStr(t *testing.T) {
	// 定义一个切片,字段为我们需要测试的字段
	type test struct {
		str  string
		sep  string
		want []string
	}
	// 实例化结构体,将我们需要测试的数据实例化
	tests := []test{
		{"a:b:c", ":", []string{"a", "b", "c"}},
		{"abcdbcfwqa", "b", []string{"a", "cd", "cfwqa"}},
		{"啊你好帅啊好帅", "啊", []string{"你好帅", "好帅"}},
	}
	// 循环进行测试
	for _, ts := range tests {
		got := SplitStr(ts.str, ts.sep)
		if !reflect.DeepEqual(got, ts.want) {
			t.Errorf("测试失败,期望:%#v,实际:%#v", ts.want, got)
		}
	}
}
`%#v`

子测试

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

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

go
package splitStr

import (
	"reflect"
	"testing"
)

func TestSplitStr(t *testing.T) {
	// 定义一个切片,字段为我们需要测试的字段
	type test struct {
		str  string
		sep  string
		want []string
	}
	// 测试用例用map存储
	tests := map[string]test{
		"case1": {"a:b:c", ":", []string{"a", "b", "c"}},
		"case2": {"abcdbcfwqa", "b", []string{"a", "cd", "cfwqa"}},
		"case3": {"啊你好帅啊好帅", "啊", []string{"你好帅", "好帅"}},
	}

	// 循环进行测试
	for name, ts := range tests {
		got := SplitStr(ts.str, ts.sep)
		if !reflect.DeepEqual(got, ts.want) {
			t.Errorf("测试失败,name: %s 期望:%#v,实际:%#v", name, ts.want, got)
		}
	}
}

然后执行结果如下:

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

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

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

代码如下:

go
package splitStr

import (
	"reflect"
	"testing"
)

func TestSplitStr(t *testing.T) {
	// 定义一个切片,字段为我们需要测试的字段
	type test struct {
		str  string
		sep  string
		want []string
	}
	// 测试用例用map存储
	tests := map[string]test{
		"case1": {"a:b:c", ":", []string{"a", "b", "c"}},
		"case2": {"abcdbcfwqa", "b", []string{"a", "cd", "cfwqa"}},
		"case3": {"啊你好帅啊好帅", "啊", []string{"", "你好帅", "好帅"}},
	}

	// 循环进行测试
	for name, ts := range tests {
		t.Run(name, func(t *testing.T) {
			got := SplitStr(ts.str, ts.sep)
			if !reflect.DeepEqual(got, ts.want) {
				t.Errorf("测试失败,name: %s 期望:%#v,实际:%#v", name, ts.want, got)
			}
		})
	}
}

执行代码如下:

go
PS E:\DEV\Go\src\code.rookieops.com\day05\splitStr> go test -v
=== RUN   TestSplitStr
=== RUN   TestSplitStr/case1
=== RUN   TestSplitStr/case2
=== RUN   TestSplitStr/case3
--- PASS: TestSplitStr (0.00s)
    --- PASS: TestSplitStr/case1 (0.00s)
    --- PASS: TestSplitStr/case2 (0.00s)
    --- PASS: TestSplitStr/case3 (0.00s)
PASS
ok      code.rookieops.com/day05/splitStr       1.400s

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

go
 go test -v -run=TestSplitStr/case3
=== RUN   TestSplitStr
=== RUN   TestSplitStr/case3
--- PASS: TestSplitStr (0.00s)
    --- PASS: TestSplitStr/case3 (0.00s)
PASS
ok      code.rookieops.com/day05/splitStr       1.039s

测试覆盖率

测试覆盖率是你的代码被测试套件覆盖的百分比。通常我们使用的都是语句的覆盖率,也就是在测试中至少被运行一次的代码占总代码的比例。

Go 提供内置功能来检查你的代码覆盖率。我们可以使用go test -cover来查看测试覆盖率。例如:

go
go test -cover
PASS
coverage: 100.0% of statements
ok      code.rookieops.com/day05/splitStr       1.151s
`-coverprofile`

go test   -coverprofile c.out

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

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

go
go tool cover -html c.out

90b02acaefc978bcb8e27a725af014c1 MD5

最近更新