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
包,测试函数的基本格式(签名)如下:
func TestName(t *testing.T){
// ...
}
测试函数的名字必须以Test
开头,可选的后缀名必须以大写字母开头,举几个例子:
func TestAdd(t *testing.T){ ... }
func TestSum(t *testing.T){ ... }
func TestLog(t *testing.T){ ... }
其中参数t
用于报告测试失败和附加的日志信息。 testing.T
的拥有的方法如下:
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
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
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)
}
}
运行测试:
PS E:\DEV\Go\src\code.rookieops.com\day05\splitStr> go test
PASS
ok code.rookieops.com/day05/splitStr 1.362s
测试组
所谓的测试组就是将上面一个一个单独的测试函数封装到一个函数中。如下:
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
的格式化方式。
子测试
如果测试用例比较多,需要定位是哪一个测试用例或者只执行某一个测试用例,需要使用子测试。
但是在使用子测试之前,可能会使用如下方法:
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)
}
}
}
然后执行结果如下:
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()
。
代码如下:
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)
}
})
}
}
执行代码如下:
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 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 test -cover
PASS
coverage: 100.0% of statements
ok code.rookieops.com/day05/splitStr 1.151s
Go还提供了一个额外的-coverprofile
参数,用来将覆盖率相关的记录信息输出到一个文件。例如:go test -coverprofile c.out
go test -coverprofile c.out
PASS
coverage: 100.0% of statements
ok code.rookieops.com/day05/splitStr 1.183s
这个文件还可以用html查看,如下:
go tool cover -html c.out