golang提供了极为简洁的编写单元测试的方式,只需几行代码,即可轻松创建出一个测试用例,并且可以直接运行。
1.testing单元测试
使用testing
可以提供自动化的测试支持,通过go test
命令能够执行形如一下结构的函数:
func TestXXX(t *testing.T)
XXX可以是任何的字符串,通常为被测试的方法名。
其中的*testing.T
包含测试打印测试日志、输出断言错误等的一些方法,此外还包括Skip用于跳过某个测试用例的方法。
下面是一个简单的待测试待函数(funcs1.go):
package unit_1
//编写一个简单的方法
func Sum(arr []int) int {
sum := 0
for v := range arr { //故意写错的..
sum += v
}
return sum
}
另外创建一个文件(funcs1_test.go)包含以下测试方法:
package unit_1
import (
"testing"
)
func TestSum(t *testing.T) {
testArr := []int{1, 3, 5, 7}
expected := 16
actual := Sum(testArr)
if actual != expected {
t.Errorf("sum result error.input:%+v expected:%d actual:%d", testArr, expected, actual)
}
}
使用如下命令执行测试操作:
go test .
结果如下:
--- FAIL: TestSum (0.00s)
funcs1_test.go:13: sum result error.input:[1 3 5 7] expected:16 actual:6
FAIL
FAIL testAnything/unit_1 0.006s
发现测试失败,说明函数存在bug。
原因是for range 单参数获取的是索引值,而我们需要的是每个数组元素的值,改正后再次运行可以发现:
ok testAnything/unit_1 0.005s
这次运行成功了!!
2.func TestMain(m *testing.M)
若被测试的方法依赖某些环境,需要首先初始化环境,测试结束后,又需要做清理工作(例如关闭连接)等等可以用TestMain()。如下:
func TestMain(m *testing.M) {
fmt.Println("init anything env....")
m.Run()
fmt.Println("handle close etc..")
}
使用go test -v
执行测试方法:
init anything env....
=== RUN TestSum
--- PASS: TestSum (0.00s)
PASS
handle close etc..
ok testAnything/unit_1 0.008s
3.介绍下go test的几个常用的参数:
-bench regexp
执行相应的 benchmarks,例如 -bench=.
-cover
开启测试覆盖率
-run regexp
只运行 regexp 匹配的函数,例如 -run=Array 那么就执行包含有 Array 开头的函数
-v
显示测试的详细命令
4.使用goland简化上述过程
使用goland可以快速构建测试用例,并且可以替代go test命令执行对应测试方法(本质其实还是go test,只不过交给了goland隐式的调用了)
还是以上面的sum方法为例:
Step.1 在需要测试的方法名出右键-> Generate…
Step.2 选择“Test for Function”
【注】:可能会出现以下提示,选择安装即可
之后会生成如下的代码:
func TestSum(t *testing.T) {
type args struct {
arr []int
}
tests := []struct {
name string
args args
want int
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Sum(tt.args.arr); got != tt.want {
t.Errorf("Sum() = %v, want %v", got, tt.want)
}
})
}
}
Step.4 在// TODO: Add test cases.处 添加测试用例即可!
其中:
name
为测试的名称,可以写序号或者对应测试用例的特点或描述。
args
为方法入参,
want
因返回值类型会有不同,也就是期望的值。
填好后如下:
func TestSum(t *testing.T) {
type args struct {
arr []int
}
tests := []struct {
name string
args args
want int
}{
// TODO: Add test cases.
{
name: "case1",
args: args{arr: []int{1, 2, 3, 4, 5, 6, 7}},
want: 28,
},
{
name: "case2",
args: args{arr: []int{1, 2, 3, 4, 5, 6}},
want: 22, //故意写错的,模拟测试失败
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Sum(tt.args.arr); got != tt.want {
t.Errorf("Sum() = %v, want %v", got, tt.want)
}
})
}
}
测试结果如下:
init anything env....
=== RUN TestSum
--- FAIL: TestSum (0.00s)
=== RUN TestSum/case1
--- PASS: TestSum/case1 (0.00s)
=== RUN TestSum/case2
--- FAIL: TestSum/case2 (0.00s)
funcs1_test.go:49: Sum() = 21, want 22
FAIL
handle close etc..
Process finished with exit code 0
|–进阶---->
1、单元测试会用的其他的方法:
【以下方法中均会打印测试log,其中带f
的是格式化的,语法参考fmt包,与printf类似】
方法名 | 作用 |
---|---|
Log、Logf | 输出信息 |
Fail、Failf | 测试失败,测试终止,相当于 Logf + FailNow |
FailNow | 测试失败,并终止测试 |
Error、Errorf | 标示出现错误,并会继续测试,相当于 Log + Fail |
Skip、Skipf | 相当于 Log[f] + SkipNow |
SkipNow | 跳过测试,测试中断 |
**【注】**当一个测试的测试函数返回时,又或者当一个测试函数调用 FailNow、 Fatal、Fatalf、SkipNow、Skip 或者 Skipf 中的任意一个时,该测试即宣告结束,只能在运行测试函数的 goroutine 中调用。上述方法是通过调用 runtime.Goexit()
来中断测试的。
2、子测试
如果需要在一个测试方法中运行另一个测试方法,可以使用子测试:
func TestSum(t *testing.T) {
type args struct {
arr []int
}
tests := []struct {
name string
args args
want int
}{
// TODO: Add test cases.
{
name: "case1",
args: args{arr: []int{1, 2, 3, 4, 5, 6, 7}},
want: 28,
},
{
name: "case2",
args: args{arr: []int{1, 2, 3, 4, 5, 6}},
want: 21, //故意写错的,模拟测试失败
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Sum(tt.args.arr); got != tt.want {
t.Errorf("Sum() = %v, want %v", got, tt.want)
}
})
}
t.Run("sub Testing", TestSum2)
}
func TestSum2(t *testing.T) {
fmt.Println("do something...")
}
全部方法测试通过才算PASS
init anything env....
=== RUN TestSum
--- PASS: TestSum (0.00s)
=== RUN TestSum/case1
--- PASS: TestSum/case1 (0.00s)
=== RUN TestSum/case2
--- PASS: TestSum/case2 (0.00s)
=== RUN TestSum/sub_Testing
do something...
--- PASS: TestSum/sub_Testing (0.00s)
PASS
handle close etc..