一. 测试概要
1.1 测试分类
Go语言的测试文件须以_test.go 结尾,包含示例函数 、单元测试函数 、基准测试函数 和主测函数 四种类型。
类型 | 格式 | 作用 |
---|
示例函数 | ExampleFoo() | 提供示例文档 | 测试函数 | TestFoo(t *testing.T) | 测试程序的一些逻辑行为是否正确 | 基准函数 | BenchmarkFoo(b *testing.B) | 测试函数的性能 | 主测函数 | TestMain(m *testing.M) | 引导一个单元测试 |
1.2 测试命令
命令格式:
go test [-c] [-i] [build flags] [packages] [flags for test binary]
-cpu : 设置测试最大 cpu 逻辑数(也就是GPM中P, 最大并行执行的 gorouting 数量, 默认等于cpu核数)-count :设置执行测试函数的次数, 默认为 1-run : 执行功能测试函数, 支持正则匹配, 可以选择测试函数或者测试文件来仅测试单个函数或者单个文件-bench :执行基准测试函数, 支持正在匹配-benchtime :基准测试最大探索时间上限-parallel :设置同一个被测试代码包中的功能测试函数的最大并发执行数-v : 是展示测试过程信
二. 示例函数
示例函数,在Goland等IDE中调用该目标函数时,会智能提示Example 示例哦👍。
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
func ExampleFib() {
fmt.Println(Fib(10))
fmt.Println(Fib(20))
fmt.Println(Fib(30))
fmt.Println(Fib(40))
fmt.Println(Fib(42))
fmt.Println(Fib(43))
}
在Fib源函数或被调实例上提示出了Example使用示例 😊😊😊~~~
三. 单元测试
单元测试(unit testing )是指对软件中的最小可测试单元进行检查和验证。
3.1 简单测试
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
actual, expect := Add(2, 1), 3
if actual != expect {
t.Errorf("测试失败:预期:%v,实际:%v", expect, actual)
return
}
}
3.2 表格驱动
func TestAdd(t *testing.T) {
type args struct {
a int
b int
}
tests := []struct {
name string
args args
want int
}{
{args: args{a: 1, b: 2}, want: 3},
{args: args{a: 1, b: 0}, want: 1},
{args: args{a: -1, b: -2}, want: -3},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.args.a, tt.args.b); got != tt.want {
t.Errorf("结果: %v, 期望: %v", got, tt.want)
}
})
}
}
3.3 测试覆盖率
执行go test --cover 查看测试覆盖率
func Score(score int) string {
switch {
case score >= 90:
return "A"
case score >= 80:
return "B"
case score >= 60:
return "C"
default:
return "D"
}
}
func TestScore(t *testing.T) {
type args struct{ a int }
tests := []struct {
name string
args args
want string
}{
{name: "测试优秀", args: args{a: 98}, want: "A"},
{name: "测试良好",args: args{a: 88}, want: "B"},
{args: args{a: 80}, want: "B"},
{args: args{a: 70}, want: "C"},
{args: args{a: 55}, want: "D"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Score(tt.args.a); got != tt.want {
t.Errorf("结果: %v, 期望: %v", got, tt.want)
}
})
}
}
查看覆盖率报告:
可将测试结果导出为测试报告,并使用cover可视化工具查看报告:
$ go test --cover -run='WithCover' -coverprofile=cover.out
$ go tool cover -html=cover.out
3.4 子测试
表格驱动中就是使用的子测试 ,子测试在对比测试中很有用,如:比对「递归斐波那契数列」耗时:
func TestFib(t *testing.T) {
t.Run("参数10", func(t *testing.T) { Fib(10) })
t.Run("参数20", func(t *testing.T) { Fib(20) })
t.Run("", func(t *testing.T) { Fib(30) })
t.Run("", func(t *testing.T) { Fib(40) })
t.Run("", func(t *testing.T) { Fib(42) })
t.Run("", func(t *testing.T) { Fib(43) })
}
$ go test -run='Fib' -v
=== RUN TestFib
=== RUN TestFib/参数10
=== RUN TestFib/参数20
=== RUN TestFib/
=== RUN TestFib/
=== RUN TestFib/
=== RUN TestFib/
--- PASS: TestFib (4.04s)
--- PASS: TestFib/参数10 (0.00s)
--- PASS: TestFib/参数20 (0.00s)
--- PASS: TestFib/
--- PASS: TestFib/
--- PASS: TestFib/
--- PASS: TestFib/
PASS
ok mytest 4.043s
四. 基准测试
基准测试即性能测试 ,用以获得代码内存占用和运行效率的性能数据。
4.1 测试性能
命令格式: go test -bench=. [-parallel=1] [-cpu=2]
func BenchmarkUUID(b *testing.B) {
for i := 0; i < b.N; i++ {
uuid.New()
}
}
$ go test -benchtime=3s -bench=UUID -run=none
goos: darwin
goarch: amd64
pkg: mytest
cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
BenchmarkUUID-4 3902818 879.0 ns/op
PASS
ok mytest 4.371s
共计运行3902818 次,平均耗时879.0 纳秒。
4.2 测试内存
命令格式: go test -benchmem -bench=<FuncName>
func BenchmarkAlloc(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Sprintf("%d", i)
}
}
$ go test -benchmem -bench=Alloc -run=none
goos: darwin
goarch: amd64
pkg: mytest
cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
BenchmarkAlloc-4 12507582 103.4 ns/op 16 B/op 1 allocs/op
PASS
ok mytest 1.398s
共计运行12507582 次,平均耗时103.4 纳秒;每次调用需分配16 字节,每次调用有1 次分配。
4.2 测试并发
以协程并发的方式执行性能测试,可与常规性能测试结果比对性能差异
func BenchmarkFibParallel(b *testing.B) {
b.SetParallelism(1)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Fib(20)
}
})
}
4.4 控制计时器
有些测试需要一定的启动和初始化时间,如果从Benchmark() 函数开始计时会很大程度上影响测试结果的精准性
func BenchmarkTimerControl(b *testing.B) {
<-time.After(time.Second)
b.ResetTimer()
var n int
for i := 0; i < b.N; i++ {
n++
}
}
五. 主测函数
每次执行单元测试时,都会由TestMain 发起,相当于测试函数的入口(main)函数 。
func TestMain(m *testing.M) {
var (
start = "安装/启动测试..."
finish = "卸载/完成测试..."
)
fmt.Println(start)
code := m.Run()
fmt.Println(finish)
os.Exit(code)
}
六. pprof监控
6.1 测试实例A
测试fmt.Sprintf ,strconv.FormatInt 和strconv.Itoa 的性能:
package main
import (
"fmt"
"strconv"
"testing"
)
func BenchmarkStrconvSprintf(b *testing.B) {
num := 10
b.ResetTimer()
for i := 0; i < b.N; i++ {
fmt.Sprintf("%d", num)
}
}
func BenchmarkStrconvFormat(b *testing.B) {
num := int64(10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
strconv.FormatInt(num, 10)
}
}
func BenchmarkStrconvItoa(b *testing.B) {
num := 10
b.ResetTimer()
for i := 0; i < b.N; i++ {
strconv.Itoa(num)
}
}
$ go test -bench=Strconv* -run=none
goos: darwin
goarch: amd64
pkg: mytest
cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
BenchmarkStrconvSprintf-4 15038674 75.47 ns/op
BenchmarkStrconvFormat-4 442853013 2.495 ns/op
BenchmarkStrconvItoa-4 468989292 2.530 ns/op
PASS
ok mytest 4.056s
6.2 测试实例B
测试以递归方式实现「斐波那契额数列」的函数性能:
package main
import (
"testing"
)
func BenchmarkNormalFib05(b *testing.B) {
for i := 0; i < b.N; i++ {
Fib(5)
}
}
func BenchmarkNormalFib10(b *testing.B) {
for i := 0; i < b.N; i++ {
Fib(10)
}
}
func BenchmarkNormalFib20(b *testing.B) {
for i := 0; i < b.N; i++ {
Fib(20)
}
}
func BenchmarkNormalFib30(b *testing.B) {
for i := 0; i < b.N; i++ {
Fib(30)
}
}
func BenchmarkNormalFib40(b *testing.B) {
for i := 0; i < b.N; i++ {
Fib(40)
}
}
func BenchmarkNormalFib42(b *testing.B) {
for i := 0; i < b.N; i++ {
Fib(42)
}
}
$ go test -bench=BenchmarkNormalFib* -run=none
goos: darwin
goarch: amd64
pkg: mytest
cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
BenchmarkNormalFib05-4 46707842 22.01 ns/op
BenchmarkNormalFib10-4 4630387 262.4 ns/op
BenchmarkNormalFib20-4 37680 34472 ns/op
BenchmarkNormalFib30-4 298 3935120 ns/op
BenchmarkNormalFib40-4 3 565669482 ns/op
BenchmarkNormalFib42-4 1 1340423143 ns/op
PASS
ok mytest 10.247s
6.3 查看性能报告
可将以上测试结果导出为测试报告,并使用pprof工具查看报告:
$ go test -bench=Strconv* -run=none -cpuprofile cpu.out -memprofile mem.out
$ go test -bench=BenchmarkNormalFib* -run=none -cpuprofile cpu.out -memprofile mem.out
$ go tool pprof -text mem.out
go tool pprof -svg cpu.out > cpu.svg
go tool pprof -svg cpu.out > cpu.pdf
$ go tool pprof -http=":8081" ./cpu.out
$ go tool pprof -http=":8081" ./mem.out
参考链接
https://studygolang.com/static/pkgdoc/pkg/testing.htm https://github.com/google/pprof/blob/master/doc/README.md
|