Go语言中自带有一个轻量级的测试框架testing和自带的go test命令来实现单元测试和性能测试。这里以main_test.go这个文件为例
1. 单元测试文件,基本原则:
文件名必须是_test.go结尾的,这样在执行go test的时候才会执行到相应的代码
你必须import testing这个包
所有的测试用例函数必须是Test开头
测试用例会按照源代码中写的顺序依次执行
测试函数TestXxx()的参数是testing.T,我们可以使用该类型来记录错误或者是测试状态
测试格式:func TestXxx (t *testing.T),Xxx部分可以为任意的字母数字的组合,但是首字母不
是小写字母[a-z],例如Testintdiv是错误的函数名。
函数中通过调用testing.T的Error, Errorf, FailNow, Fatal, FatalIf方法,说明测试不通过,调用Log方法用来记录测试的信息。
2. 功能测试函数
2.1 测试函数中的某条测试用例执行结果与预期不符时,调用t.Error()或t.Errorf()方法记录日志并标记测试失败:
func TestCompareIdenticalSlice(t *testing.T) {
sa := "aaa"
sb := "bbb"
if strings.Compare(sa, sb) != 0 {
t.Error("b != b")
}
}
2.2 使用t.Fatal()和t.Fatalf()方法,在某条测试用例失败后就跳出该测试函数
func TestCompareIdenticalSlice(t *testing.T) {
sa := "aaa"
sb := "bbb"
if strings.Compare(sa, sb) != 0 {
t.Fatal("error")
}
s1 := "aaa1"
s2 := "bbb"
if strings.Compare(s1, s2) != 0 {
t.Errorf("errorf")
}
}
2.3 使用t.Skip()和t.Skipf()方法,跳过某条测试用例的执行如下会跳过TestCompareIdenticalSlice 执行 TestCompareIdenticalSlice1测试用例
func TestCompareIdenticalSlice(t *testing.T) {
sa := "aaa"
sb := "bbb"
if strings.Compare(sa, sb) != 0 {
t.Skip("slow test; skipping")
}
s1 := "aaa1"
s2 := "bbb"
if strings.Compare(s1, s2) != 0 {
t.Errorf("errorf")
}
}
func TestCompareIdenticalSlice1(t *testing.T) {
sa := "aaa"
sb := "bbb"
if strings.Compare(sa, sb) != 0 {
t.Errorf("errorf2")
}
s1 := "bbb"
s2 := "bbb"
if strings.Compare(s1, s2) != 0 {
t.Errorf("errorf2")
}
}
2.4 使用t.Parallel()标记需要并发执行的测试函数
func gorw() {
sa := "aaa"
sb := "bbb"
if strings.Compare(sa, sb) != 0 {
fmt.Println("dfdf")
}
}
func TestCompareIdenticalSlice(t *testing.T) {
t.Parallel()
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
gorw()
}()
wg.Wait()
}
sync.WaitGroup只有3个方法,Add(),Done(),Wait()。其中Done()是Add(-1)的别名。简单的来说,使用Add()添加计数,Done()减掉一个计数,计数不为0, 阻塞Wait()的运行。
要注意的有一点。sync文档已经说明了的,The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished.也就是说,在运行main函数的goroutine里运行Add()函数,在其他的goroutine里面运行Done()函数。
3. 性能测试函数
性能测试函数需要接收*testing.B类型的单一参数b,性能测试函数中需要循环b.N次调用被测函数。testing.B 类型用来管理测试时间和迭代运行次数,也支持和testing.T相同的方式管理测试状态和格式化的测试日志,不一样的是testing.B的日志总是会输出。
3.1 在函数中调用t.ReportAllocs() ,启用内存使用分析
func BenchmarkCompareIdenticalSlice(b *testing.B) {
b.ReportAllocs()
sa := "aaa"
sb := "bbb"
if strings.Compare(sa, sb) != 0 {
fmt.Println("dfdf")
}
}
3.2 通过 b.StopTimer() 、b.ResetTimer() 、b.StartTimer()来停止、重置、启动 时间经过和内存分配计数
func BenchmarkCompareIdenticalSlice(b *testing.B) {
b.ResetTimer()
b.StartTimer()
for i := b.N - 1; i >= 0; i-- {
fmt.Println(i)
}
b.StopTimer()
}
3.3 调用b.SetBytes()记录在一个操作中处理的字节数,最终会显示执行的次数,每次执行所用时间ns/op 以及MB/s
func BenchmarkCompareIdenticalSlice(b *testing.B) {
b.SetBytes(int64(len("fieldsInput")))
for i := 0; i < b.N; i++ {
fmt.Println("fieldsInput")
}
}
3.4 通过b.RunParallel()方法和 *testing.PB类型的Next()方法来并发执行被测对象,最终会显示执行的次数,每次执行所用时间ns/op
func BenchmarkCompareIdenticalSlice(b *testing.B) {
var v atomic.Value
v.Store(new(int))
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
x := v.Load().(*int)
if *x != 0 {
b.Fatalf("wrong value: got %v, want 0", *x)
}
}
})
}
4. go test 工具
打开终端,进入需要测试的包所在的目录执行 go test,或者直接执行go test $pkgnamein_gopath即可对指定的包执行测试。通过形如go test github.com/tabalt/...的命令可以执行$GOPATH/github.com/tabalt/目录下所有的项目的测试。go test std命令则可以执行Golang标准库的所有测试。
4.1 如果想查看执行了哪些测试函数及函数的执行结果,可以使用-v参数:
E:\GoWorks>go test -v
=== RUN TestCompareIdenticalSlice
--- FAIL: TestCompareIdenticalSlice (0.00s)
main_test.go:27: sa != sb
FAIL
exit status 1
FAIL _/E_/GoWorks 0.094s
4.2 假设我们有很多功能测试函数,但某次测试只想执行其中的某一些,可以通过-run参数,使用正则表达式来匹配要执行的功能测试函数名。如下面指定参数后,功能测试函数TestHello不会执行到。
E:\GoWorks>go test -v -run="TestCompareIdenticalSlice"
=== RUN TestCompareIdenticalSlice
--- FAIL: TestCompareIdenticalSlice (0.00s)
main_test.go:27: sa != sb
FAIL
exit status 1
FAIL _/E_/GoWorks 0.090s
4.3 性能测试函数默认并不会执行,需要添加-bench参数,并指定匹配性能测试函数名的正则表达式;例如,想要执行某个包中所有的性能测试函数可以添加参数-bench . 或 -bench=.,如果执行指定的性能测试函数参数为 -bench="BenchXXX"。
(1) E:\GoWorks>go test -bench=.
BenchmarkCompareIdenticalSlice-8 1000000000 2.46 ns/op
PASS
ok _/E_/GoWorks 2.789s
(2) E:\GoWorks>go test -bench=BenchmarkCompareIdenticalSlice2
BenchmarkCompareIdenticalSlice2-8 1000000000 2.33 ns/op
PASS
ok _/E_/GoWorks 2.577s
4.4 想要查看性能测试时的内存情况,可以再添加参数-benchmem:
E:\GoWorks>go test -bench="BenchmarkCompareIdenticalSlice2" -benchmem
BenchmarkCompareIdenticalSlice2-8 1000000000 2.47 ns/op 0 B/op 0 allocs/op
PASS
ok _/E_/GoWorks 2.800s
5. 通过go help test可以看到go test的使用说明:
格式形如:
go test [-c] [-i] [build flags] [packages] [flags for test binary]
参数解读:
1. -c : 编译go test成为可执行的二进制文件,但是不运行测试。
2. -i : 安装测试包依赖的package,但是不运行测试。
关于build flags,调用go help build,这些是编译运行过程中需要使用到的参数,一般设置为空
关于packages,调用go help packages,这些是关于包的管理,一般设置为空
关于flags for test binary,调用go help testflag,这些是go test过程中经常使用到的参数
3. -test.v : 是否输出全部的单元测试用例(不管成功或者失败),默认没有加上,所以只输出失败的单元测试用例。
E:\GoWorks>go test -v
4 -test.run pattern: 只跑哪些单元测试用例 :
E:\GoWorks>go test -run="TestCompareIdenticalSlice"
5 -test.bench patten: 只跑那些性能测试用例
E:\GoWorks> go test -bench="."
6 -test.benchmem : 是否在性能测试的时候输出内存情况:
E:\GoWorks> go test -bench="BenchmarkCompareIdenticalSlice2" -benchmem
7 -test.benchtime t : 性能测试运行的时间,默认是1s
8 -test.cpuprofile cpu.out : 是否输出cpu性能分析文件
E:\GoWorks>go test -run = "main_test.go" -cpuprofile cpu.out
9 -test.memprofile mem.out : 是否输出内存性能分析文件
E:\GoWorks> go test -run = "main_test.go" -memprofile mem.out
10 -test.blockprofile block.out : 是否输出内部goroutine阻塞的性能分析文件
E:\GoWorks> go test -run = "main_test.go" -blockprofile mem.out
11 -test.memprofilerate n : 内存性能分析的时候有一个分配了多少的时候才打点记录的问题。这个参数就是设置打点的内存分配间隔,也就是profile中一个sample代表的内存大小。默认是设置为512 * 1024的。如果你将它设置为1,则每分配一个内存块就会在profile中有个打点,那么生成的profile的sample就会非常多。如果你设置为0,那就是不做打点了。
你可以通过设置memprofilerate=1和GOGC=off来关闭内存回收,并且对每个内存块的分配进行观察。
12 -test.blockprofilerate n: 基本同上,控制的是goroutine阻塞时候打点的纳秒数。默认不设置就相当于-test.blockprofilerate=1,每一纳秒都打点记录一下
13 -test.parallel n : 性能测试的程序并行cpu数,默认等于GOMAXPROCS。
14 -test.timeout t : 如果一个测试运行的时间超过t,那么panic。 默认值是10分钟(1000米)。
E:\GoWorks>go test -run="main_test.go" -timeout 1000s
15 -test.count n: 运行每个测试和基准测试n次(默认1)。 如果-cpu被设置,则为每个GOMAXPROCS值运行n次。 示例总是运行一次。
E:\GoWorks>go test -bench="BenchmarkCompareIdenticalSlice2" -count=10
16 -test.cpu 1,2,4 : 程序运行在哪些CPU上面,使用二进制的1所在位代表,和nginx的nginxworkercpu_affinity是一个道理
17 -test.short : 将那些运行时间较长的测试用例运行时间缩短
E:\GoWorks>go test -run="main_test.go" -short=false
18 -outputdir directory: 将输出文件从指定目录的概要文件中放置
E:\GoWorks>go test -bench="BenchmarkCompareIdenticalSlice2" -test.blockprofile block.out -outputdir="E:/GoWorks/data"
19 -trace trace.out : 关于执行跟踪向文件中写入指定在退出之前
E:\GoWorks>go test -bench="BenchmarkCompareIdenticalSlice2" -trace trace.out
20 -test.memprofilerate rate:设置内存分析率(见运行时)
21 -test.mutexprofile string:在执行后将一个互斥锁争用配置文件写入到指定的文件中
22 -test.mutexprofilefraction int :如果大于0,则调用runtime.setmutexprofile分式()