在看开源项目的同学,如果你已经开始注意 _test 文件了的话,那么恭喜你,你将开启单元测试的大门了。
关于单元测试,Go 语言它有一套属于自己的单元测试和性能测试系统。
相比其他语言,这块有着非常强的优势,我们只需要写很少的代码就能实现对代码的测试。
这篇文章我们主要讲解单元测试,下一篇我们再讲基准测试。
大家可还记得我们读书时,翻开《语文》书目录的时候,总能看到第一单元,第二单元么?
像下面这张图:
图片来自网络
你会发现,一本书作者把它被分成了很多个单元,当我们学完所有单元就意味着这个学期学完了,这本书学完了。
我们的程序也是这样,我们的一个项目,大都会被分成不同的模块,不同模块里面又会有不同的单元。
当我们的每一个单元都测试OK,也就意味着,这个项目在正常情况下使用,应该没啥问题了。
当然也有不正常的时候,那就说明卡BUG了,自求多福吧,哈哈。
所以在大型项目里面,单元测试是必不可少的,小项目就看项目需要了。
我们只需要在我们的工程里面创建以 _test 结尾的 go 文件即可,一个单元测试文件就创建好了。
如果是在 GoLand 里面他会自动给识别,加上特殊的标记,比如下面这样的:
大部分 Go语言的开源项目,这样的文件就特别多,比如这样:
这是 kubernetes 的 Github 截图。
首先我们需要写一个简单的业务文件,方便我们去测试,于是我新建了一个目录 utils,然后新建一个 string.go 文件:
package utils
// JointString 拼接字符串
func JointString(a string,b string) string {
return a+b
}
我写了一个非常简单的方法,就是拼接两个字符串。
然后我们再建一个单元测试文件。
我们喜欢把单元测试的文件名和被测试的文件关联起来,喜欢在测试的文件后面加 _test ,所以我们的文件名 就是 string_test.go 。
文件建好之后,就需要在里面去编写我们的测试代码了。
1、其实我们的单元测试代码,和我们的普通代码没太大的区别,你正常写就好了。
2、唯一的区别就是,我们需要用到 testing 包里面的相关 API 来进行干预测试结果。
3、同时方法名必须要 Test 开头。
当然我们也比较喜欢把测试方法和被测试的方法关联起来(非硬性规定),于是我们就这样写一个最简单的测试代码:
package utils
import (
"fmt"
"testing"
)
func TestJointString(t *testing.T) {
fmt.Println(JointString("a","b"))
}
这里我们还没用到任何的 testing 里面的API,只是在测试方法里面调用了我们的 JointString 方法。
如果你用的 GoLand 开发,就可以直接点击方法旁边的绿色图标即可运行:
你也可以在命令行里面运行,cd 到我们的 utils 目录下面,执行 go test 即可:
go test -v
// 执行结果
$ go test -v
=== RUN TestJointString
ab
--- PASS: TestJointString (0.00s)
PASS
ok map-demo/utils 0.328s
-v 是让控制台输出测试详细的流程。
现在你已经会了基本的单元测试创建方法,接下来进阶下。
我们需要对我们的测试结果进行预判,否则只是输出一个结果那肯定达不到测试的目的。
这就需要了解下我们的 testing.T 里面的 API 了,我们常用的方法有:
方法 | 备注 |
---|---|
Log | 打印日志,同时结束测试 |
Logf | 格式化打印日志,同时结束测试 |
Error | 打印错误日志,同时结束测试 |
Errorf | 格式化打印错误日志,同时结束测试 |
Fatal | 打印致命日志,同时结束测试 |
Fatalf | 格式化打印致命日志,同时结束测试 |
接下来我们进行改造我们的测试方法:
func TestJointString(t *testing.T) {
if JointString("a","b")!="ab" {
t.Error("与我们预判的结果不一致")
}
// 故意写一个错误的预判
if JointString("g","b")!="gob" {
t.Error("与我们预判的结果不一致")
}
}
//执行结果
$ go test -v
=== RUN TestJointString
string_test.go:13: 与我们预判的结果不一致
--- FAIL: TestJointString (0.00s)
FAIL
exit status 1
FAIL map-demo/utils 1.851s
你会发现测试结果就是 FAIL 了。
我们一般对一个方法不会是只有一两个预判。
如果有 N 个预判,你还继续使用上面这种 if 来判断,会有点 LOW。
于是我们可以对我们的测试代码再次升级。
我们可以模拟一个把测试数据和预判结果放到一个二维数组里面来 for 遍历,这种方式我们也叫 表驱动测试模式 。
上代码:
func TestJointString1(t *testing.T) {
type args struct {
a string
b string
}
tests := []struct {
name string
args args
want string
}{
// 这里编写我们的预判逻辑
{"testA", args{"d","v"}, "dv"},
{"testB", args{"c","d"}, "cd"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := JointString(tt.args.a, tt.args.b); got != tt.want {
t.Errorf("JointString() = %v, want %v", got, tt.want)
}
})
}
}
// 执行结果
$ go test -v
=== RUN TestJointString1
=== RUN TestJointString1/testA
=== RUN TestJointString1/testB
--- PASS: TestJointString1 (0.00s)
--- PASS: TestJointString1/testA (0.00s)
--- PASS: TestJointString1/testB (0.00s)
PASS
ok map-demo/utils 0.437s
我们每次只需要调整我们中间的预判逻辑即可。
你学废了么?