这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记,青训营day2主要介绍了go的工程化实践,本篇blog将集中总结go单元测试相关内容,下面先看一下go单测的基本写法。
单元测试基本用法
go test对文件名字有一定要求,单测文件必须以_test.go为结尾,例如被测文件名字为math.go,对应单测文件名字就是math_test.go。
下面创建一个matht包,写一下对应的的测试case
package matht
import (
"math"
"math/rand"
)
func Abs(x float64) float64 {
return math.Abs(x)
}
func Max(x, y float64) float64 {
return math.Max(x, y)
}
func Min(x, y float64) float64 {
return math.Min(x, y)
}
func RandInt() int {
return rand.Int()
}
在同一个package下创建matht_test.go
package matht
import "testing"
func TestAbs(t *testing.T){
got := Abs(-1)
if got != 1{
t.Errorf("Abs(-1) = %f; want 1", got)
}
}
test函数有几个要注意的点
- 函数名必须以Test开头,例如测试Abs则测试函数名字为TestAbs
- 参数必须为*testing.T类型
然后执行go test即可运行单测(go test还支持-v,-count,-cover等flag)
go test ./...
go test ./... -v 显示细节
go test ./... -count=2 单测运行两次
go test ./... -cover 测试覆盖率
为了提高测试可靠性,可以在一个case中运行多个测试用例,下面一个TestAbs为例
func TestAbs(t *testing.T){
tests := []struct{
value float64
want float64
}{
{-1, 1},
{1, 1},
{0, 0},
}
for _, tt := range tests{
if got := Abs(tt.value); got != tt.want{
t.Errorf("Abs() = %f, want %v", got, tt.want)
}
}
}
到这里可以发现go提供的基础测试包相对于jest这种完善的测试库是比较粗糙的,只有最基本的功能。
使用更完善的断言库
go社区提供了assert等功能更丰富的断言库,毕竟原生的if error代码比较难看。下面使用assert优化一下前面的测试用例
for _, tt := range tests{
got := Abs(tt.value)
assert.Equal(t, got, tt.want)
}
除了Equal,assert还提供了NotEqual,Less,True,Nil等常用的断言功能,基本可以满足日常测试需要。
mock测试
简单解释一下mock,后端的mock和前端的mock差不多,有时候我们需要测试一段代码,但是该代码可能涉及连接数据库/redis等功能,这一部分功能并不包括在单测内(单测并不关心外部服务的状态,只专注于函数功能),所以需要对连接数据库的部分进行替换,这就是mock。
下面这个例子用来mock一个读取文件的函数,毕竟在测试过程中并不能真的去读一个文件(一方面是读文件太慢,另一方面是文件并不包含在单测范围内),monkey库直接替换掉了被mock函数的地址。
func TestProcessFirstLineWithMock(t *testing.T){
monkey.Patch(ReadFirstLine, func() string{
return "line110"
})
defer monkey.Unpatch(ReadFirstLine)
line := ProcessFirstLine()
assert.Equal(t, "line000", line)
}
自动生成测试用例
看完前面的代码可以发现单测的文件格式比较固定,都是给对应的函数生成测试函数,那么固定的部分就可以用工具生成,开发者只需要关注测试的核心部分就可以了(对于前端来说测试的情况比较复杂,自动生成的意义可能不是特别大)。
下面使用gotests自动生成测试用例
go get -u github.com/cweill/gotests/...
//将gotests配置到path
gotests -all -w .
然后只需要补全测试代码核心部分即可
benchmark性能测试
和单测要求一样,benchmark测试要求测试函数必须以Benchmark开头。
下面测试一下rand函数
func BenchmarkRandInt(b *testing.B) {
for i := 0; i < b.N; i++ {
RandInt()
}
}
运行go test
go test -bench=".*" ./...
结果如下
goos: windows
goarch: amd64
pkg: study/matht
BenchmarkRandInt-8 70540101 16.5 ns/op
PASS
ok study/matht 1.613s
解释一下输出的几个字段
- BenchmarkRandInt-8说明有8个cpu参与了测试
- 70540101 是循环次数
- 16.5 ns/op 每次循环消耗16.8纳秒(移动端的u雀实不太行)
summary
总结一下go单测的重点内容
- go的单测和其他语言差不多,对于函数及文件名有特定的规范
- go虽然提供了默认test库,但是功能比较简陋,配合asset等库实现更强大的功能。
- mock是单测中的重要部分
- 对于性能敏感的函数需要进行benchmark测试