在golang语言中,通过go test和一些特殊的规则能快速实现单元测试。再正式介绍之前,先说一个坑。
坑:相同package中不同文件里的函数调用
在如下所示的目录结构中,我希望从main中调用add.go里的Add函数实现加法功能。
- test
-- main.go
-- add.go
然而通过go run main.go后,一直提示add函数未定义,而Add的确是定义过了。
# command-line-arguments
./main.go:9:14: undefined: Add
排查后才发现这种目录结构和场景时,不能使用go run main.go进行编译执行,而应该使用:
go run main.go add.go //需要加上全部文件
所以建议大家使用类似如下所示的目录结构进行项目开发,main.go文件单独放在主目录下,其他文件放在utils等新的目录中,使用import关键词导入包。
- test
-- main.go
- utils
-- add.go
下面我们正式讲解golang项目的测试代码。优秀的开发团队会针对重要的函数进行单元测试,同时良好的单元测试能够保证代码稳定性,为今后添加新功能提供便利。
逻辑测试
golang语言提供go test命令进行功能测试,即测试函数的正确性。下面用一个简单的示例带领大家逐步熟悉。假设我们写了一个utils的工具库,里面包含一个int型加法函数,需要我们进行测试。加法函数如下:
package utils
func AddInt(a int, b int) int {
return a + b
}
那如何对这个函数进行测试呢?需要编写测试程序,同时该测试程序需要按照一定的规则,以便于利用go test进行自动化编译运行。
1、在add.go的同级目录下心间add_test.go测试文件;测试文件的名称必须是源文件加上 “_test.go” 的文件,go test能够自动识别。即
- test
-- main.go
- utils
-- add.go
-- add_test.go
2、在add_test.go中编写TestAdd测试函数;测试函数名必须是“Test”前缀加上源函数名。
package utils
import "testing"
import "reflect"
func TestAddInt(t *testing.T){ //参数必须为 t *testing.T
got := AddInt(2, 4)
want:= 5 //6 != 5 测试结果为失败
if !reflect.DeepEqual(want, got) {//深度比较,简单的数据可以直接用==比较
t.Errorf("excepted:%v, got:%v", want, got)//t.Errorf用于判断测试是否
//通过,不可省略,否则程序如果顺利执行完则判定为测试通过。
}
}
在utils目录下执行
go test -v //完成自动化测试
如果一个文件里有很多个函数都需要测试,那可以按照上述1和2规则完成测试函数后进行测试。同学们可是尝试添加一个AddFloat函数并完成测试函数。
往往一组测试用例是不够的,所以针对每个函数需要多组测试用例进行测试。golang提供测试组以批量完成测试,同时也支持测试单个用例。简单的说就是对将测试用例分成组,同时起名。遍历完成批量测试,制定名称完成单个测试。
package utils
import "testing"
import "reflect"
func TestAddInt(t *testing.T){
type testCase struct {
addA int
addB int
want int //期望的计算结果
}
testGroup := map[string]testCase{
"case1": testCase{1,2,3},
"case2": testCase{2,3,5},
"case3": testCase{3,4,7},
}
for key, v := range testGroup { //遍历
t.Run(key, func (t *testing.T){
got := AddInt(v.addA, v.addB)
want:= v.want
if !reflect.DeepEqual(want, got) { //比较
t.Errorf("excepted:%v, got:%v", want, got)
}
})
}
}
上述代码是批量测试,而如果需要单独测试某个示例,则只需要通过map的name进行控制即可。通过执行go test -v,得到如下结果。
➜ utils go test -v
=== RUN TestAddInt
=== RUN TestAddInt/case3
=== RUN TestAddInt/case1
=== RUN TestAddInt/case2
--- PASS: TestAddInt (0.00s)
--- PASS: TestAddInt/case3 (0.00s)
--- PASS: TestAddInt/case1 (0.00s)
--- PASS: TestAddInt/case2 (0.00s)
PASS
ok day1/common/utils 0.006s
如果测试全部通过,则说明函数功能没有问题;反之需要检查函数逻辑。
性能测试
同逻辑测试类似,我们只需要在测试文件中加上对应的基准测试函数即可,基准函数名称以Benchmark开头,则在本例中为BenchmarkAddInt,具体函数如下:
func BenchmarkAddInt(b *testing.B) {
for i := 0; i < b.N; i++ {
AddInt(12, 13)
}
}
其中N是testing.B结构体中的一个变量,可以认为是测试函数AddInt在固定时间内可执行的次数。例如如下命令完成测试:
go test -bench=AddInt -benchmem //即对AddInt函数进行基准测试
输出结果为:
➜ utils go test -bench=AddInt -benchmem
goos: darwin //操作系统
goarch: amd64 //架构
pkg: day1/common/utils
BenchmarkAddInt-12 1000000000 0.261 ns/op 0 B/op 0 allocs/op
PASS
ok day1/common/utils 0.301s
如上所示,在12核的机器上,执行1000000000次AddInt,每次操作耗费时间0.261纳秒,空间0B,每次不分配空间,因为我们使用的是内置的int型,不需要分配内存。同时本次测试PASS。