单元测试
Go语言为我们提供了测试框架,以便帮助我们更容易的进行单元测试,但是要使用这个框架,需要遵循如下几点规则:
1、含有单元测试代码的go文件必须以_test.go结尾,Go语言测试工具只认符合这个规则的文件
2、单元测试文件名_test.go前面的部分最好是被测试的方法所在go文件的文件名,比如例子中是wxidcmdqueue_test.go。
3、单元测试的函数名必须以Test开头,是可导出公开的函数。
4、测试函数的签名必须接收一个指向testing.T类型的指针,并且不能返回任何值。
5、函数名最好是Test+要测试的方法函数名,比如例子中是TestPushCmdQueue,表示测试的是PushCmdQueue这个这个函数。
虽然可以用 fmt.printf etc打印信息, 但无法进行log信息与测试内容的分类控制(-v 无效)
强烈建议用
t.Log t.Logf // 正常信息
t.Error t.Errorf // 测试失败信息
t.Fatal t.Fatalf // 致命错误, 测试程序退出的信息
项目源代码wxidcmdqueue.go
//GetWxidCmdQueueInstance 获取指令队列对象
func GetWxidCmdQueueInstance() *WxidCmdQueue {
once.Do(func() {
instance = &WxidCmdQueue{
wxidcmdqueue: &sync.Map{},
}
})
return instance
}
//PushCmdQueue 加入指令
func (wq *WxidCmdQueue) PushCmdQueue(wxid string, data []byte) {
var cmd MatrixCmd
jsondata, err := simplejson.NewJson(data)
if err != nil {
// mlog.Error("cmd content not json")
return
}
cmd.CmdContent = data
cmd.Priority = jsondata.Get("cmd_type").MustInt(0)
queue, ok := wq.wxidcmdqueue.Load(wxid)
if ok {
queue.(*MatrixCmdHeap).Push(&cmd)
} else {
mcheap := &MatrixCmdHeap{}
mcheap.Push(&cmd)
wq.wxidcmdqueue.Store(wxid, mcheap)
}
}
//PopCmdQueue 弹出指令
func (wq *WxidCmdQueue) PopCmdQueue(wxid string) interface{} {
queue, ok := wq.wxidcmdqueue.Load(wxid)
if ok {
ret := queue.(*MatrixCmdHeap).Pop()
if ret != nil {
return ret.(*MatrixCmd).CmdContent
}
}
return nil
}
//GetCmdQueueLen 获取wxid对应的指令队列长度
func (wq *WxidCmdQueue) GetCmdQueueLen(wxid string) int {
queue, ok := wq.wxidcmdqueue.Load(wxid)
if ok {
return queue.(*MatrixCmdHeap).GetLength()
}
return 0
}
单元测试文件以_test.go结尾。
测试文件 wxidcmdqueue_test.go
func formatCmd(wxid string, cmdtype int) []byte {
json := simplejson.New()
json.Set("wx_id", wxid)
json.Set("cmd_type", cmdtype)
ret, err := json.Encode()
if err != nil {
return nil
}
return ret
}
func TestPushCmdQueue(t *testing.T) {
mCmdQueue := GetWxidCmdQueueInstance()
for cmdtype := 20010; cmdtype > 20000; cmdtype-- {
cmd := formatCmd("wxid", cmdtype)
mCmdQueue.PushCmdQueue("wxid", cmd)
}
if mCmdQueue.GetCmdQueueLen("wxid") != 10 {
t.Error("cmdqueue len error")
}
}
func TestPopCmdQueue(t *testing.T) {
mCmdQueue := GetWxidCmdQueueInstance()
mCmdQueue.CleanCmdQueueLen("wxid")
for cmdtype := 20010; cmdtype > 20000; cmdtype-- {
cmd := formatCmd("wxid", cmdtype)
mCmdQueue.PushCmdQueue("wxid", cmd)
}
for ctype := 20001; ctype <= 20010; ctype++ {
mCmd := mCmdQueue.PopCmdQueue("wxid")
mCmdContent, err := simplejson.NewJson(mCmd.([]byte))
if err != nil {
t.Error("cmd not json")
} else {
cmdtype := mCmdContent.Get("cmd_type").MustInt(0)
if cmdtype != ctype {
t.Error("cmd type error", cmdtype, "---", ctype)
}
}
}
}
go test -run TestPushCmdQueue 运行指定的测试函数
e:\Go_workspace\src\matrix\cmdqueue>go test -v -run TestPushCmdQueue
=== RUN TestPushCmdQueue
--- PASS: TestPushCmdQueue (0.00s)
PASS
ok matrix/cmdqueue 0.017s
go test -v 运行整个测试文件
e:\Go_workspace\src\matrix\cmdqueue>go test -v
=== RUN TestPushCmdQueue
--- PASS: TestPushCmdQueue (0.00s)
=== RUN TestPopCmdQueue
--- PASS: TestPopCmdQueue (0.00s)
PASS
ok matrix/cmdqueue 0.017s
如果其中存在错误
1、向指令优先级队列插入10个指令,判断写成11个
2、弹出队列时如果指令号对应不上
e:\Go_workspace\src\matrix\cmdqueue>go test -v
=== RUN TestPushCmdQueue
--- FAIL: TestPushCmdQueue (0.00s)
wxidcmdqueue_test.go:29: cmdqueue len error
=== RUN TestPopCmdQueue
--- FAIL: TestPopCmdQueue (0.00s)
wxidcmdqueue_test.go:55: cmd type error 20001 --- 20001
wxidcmdqueue_test.go:55: cmd type error 20002 --- 20002
wxidcmdqueue_test.go:55: cmd type error 20003 --- 20003
wxidcmdqueue_test.go:55: cmd type error 20004 --- 20004
wxidcmdqueue_test.go:55: cmd type error 20005 --- 20005
wxidcmdqueue_test.go:55: cmd type error 20006 --- 20006
wxidcmdqueue_test.go:55: cmd type error 20007 --- 20007
wxidcmdqueue_test.go:55: cmd type error 20008 --- 20008
wxidcmdqueue_test.go:55: cmd type error 20009 --- 20009
wxidcmdqueue_test.go:55: cmd type error 20010 --- 20010
FAIL
exit status 1
FAIL matrix/cmdqueue 0.016s
项目工程下的单元测试
将项目下的所有包进行单元测试
E:\Go_workspace\src\matrix>go test matrix/cmdqueue matrix/datamanager
ok matrix/cmdqueue (cached)
ok matrix/datamanager 0.612s
测试覆盖率
测试覆盖率要多加一个参数-coverprofile,完整的命令为:go test -v -coverprofile=c.out,-coverprofile是指定生成的覆盖率文件。
e:\Go_workspace\src\matrix\cmdqueue>go test -v -run TestPushCmdQueue -coverprofile=c.out
=== RUN TestPushCmdQueue
--- PASS: TestPushCmdQueue (0.00s)
PASS
coverage: 63.0% of statements
ok matrix/cmdqueue 0.018s
e:\Go_workspace\src\matrix\cmdqueue>go test -v -coverprofile=c.out
=== RUN TestPushCmdQueue
--- PASS: TestPushCmdQueue (0.00s)
=== RUN TestPopCmdQueue
--- PASS: TestPopCmdQueue (0.00s)
PASS
coverage: 94.4% of statements
ok matrix/cmdqueue 0.015s
生成HTML测试覆盖率文件
go tool cover -html=c.out -o coverage.html
基准测试
基准测试主要是通过测试CPU和内存的效率问题,来评估被测试代码的性能,进而找到更好的解决方案。比如链接池的数量不是越多越好,那么哪个值才是最优值呢,这就需要配合基准测试不断调优了。基准测试的规则:
1、基准测试的代码文件必须以_test.go结尾。
2、基准测试的函数必须以Benchmark开头,必须是可导出的。
3、基准测试函数必须接受一个指向Benchmark类型的指针作为唯一参数。
4、基准测试函数不能有返回值。
5、b.ResetTimer是重置计时器,这样可以避免for循环之前的初始化代码的干扰。最后的for循环很重要,被测试的代码要放到循环里。
6、b.N是基准测试框架提供的,表示循环的次数,因为需要反复调用测试的代码,才可以评估性能。\
有些测试需要一定的启动和初始化时间,如果从Benchmark()函数开始计时会很大程度上影响测试结果的精准性。testing.B提供了一系列的方法可以方便地控制计时器,从而让计时器只在需要的区间进行测试。我们通过下面的代码来了解计时器的控制。
// 重置计时器
b.ResetTimer()
// 停止计时器
b.StopTimer()
// 开始计时器
b.StartTimer()
func BenchmarkPushCmdQueue(b *testing.B) {
mCmdQueue := GetWxidCmdQueueInstance()
b.ResetTimer()
for wxidnum := 0; wxidnum < b.N; wxidnum++ {
wxid := fmt.Sprintf("wxid_%d", wxidnum)
for cmdtype := 20010; cmdtype > 20000; cmdtype-- {
cmd := formatCmd(wxid, cmdtype)
mCmdQueue.PushCmdQueue(wxid, cmd)
}
}
}
func BenchmarkPopCmdQueue(b *testing.B) {
mCmdQueue := GetWxidCmdQueueInstance()
for wxidnum := 0; wxidnum < b.N; wxidnum++ {
wxid := fmt.Sprintf("wxid_%d", wxidnum)
for cmdtype := 20010; cmdtype > 20000; cmdtype-- {
cmd := formatCmd(wxid, cmdtype)
mCmdQueue.PushCmdQueue(wxid, cmd)
}
}
b.ResetTimer()
for wxidnum := 0; wxidnum < b.N; wxidnum++ {
wxid := fmt.Sprintf("wxid_%d", wxidnum)
for cmdtype := 20010; cmdtype > 20000; cmdtype-- {
mCmdQueue.PopCmdQueue(wxid)
}
}
}
测试结果
e:\Go_workspace\src\matrix\cmdqueue>go test -bench=. -run=none
goos: windows
goarch: amd64
pkg: matrix/cmdqueue
BenchmarkPushCmdQueue-8 30000 45200 ns/op
BenchmarkPopCmdQueue-8 500000 2044 ns/op
PASS
ok matrix/cmdqueue 27.442s
函数后面的-8表示运行时对应的GOMAXPROCS的值。
接着的30000/500000和表示运行for循环的次数,也就是调用被测试代码的次数。
最后的45200 ns/op和2044 ns/op表示每次需要花费的时间。
还可以通过-benchtime参数可以自定义测试时间。默认是1秒,也就是1秒的时间,调用30000/500000次,每次调用花费45200/2044纳秒。如果想让测试运行的时间更长,可以通过-benchtime指定,比如2秒。可以发现,我们加长了测试时间,测试的次数变多了,但是最终的性能结果:每次执行的时间,并没有太大变化,意义不大,所以使用默认值就好。
e:\Go_workspace\src\matrix\cmdqueue>go test -bench=. -benchtime=2s -run=none
goos: windows
goarch: amd64
pkg: matrix/cmdqueue
BenchmarkPushCmdQueue-8 100000 45690 ns/op
BenchmarkPopCmdQueue-8 1000000 2186 ns/op
PASS
ok matrix/cmdqueue 60.194s
内存
-benchmem可以提供每次操作分配内存的次数,以及每次操作分配的字节数。
e:\Go_workspace\src\matrix\cmdqueue>go test -bench=. -run=none -benchmem
goos: windows
goarch: amd64
pkg: matrix/cmdqueue
BenchmarkPushCmdQueue-8 30000 44566 ns/op 26554 B/op 329 allocs/op
BenchmarkPopCmdQueue-8 1000000 2099 ns/op 23 B/op 2 allocs/op
PASS
ok matrix/cmdqueue 100.010s
26554 B/op 代表每次操作分配26554B的内存大小。
329 allocs/op 代表每次操作进行329次内存分配。
CPU
cpuprofile是表示生成的cpu profile文件
e:\Go_workspace\src\matrix\cmdqueue>go test -bench=".*" -cpuprofile=cpu.prof ./
goos: windows
goarch: amd64
pkg: matrix/cmdqueue
BenchmarkPushCmdQueue-8 30000 48366 ns/op
BenchmarkPopCmdQueue-8 500000 2024 ns/op
PASS
ok matrix/cmdqueue 26.987s
:\Go_workspace\src\matrix\cmdqueue>go tool pprof cmdqueue.test.exe cpu.prof
File: cmdqueue.test.exe
Type: cpu
Time: Nov 15, 2018 at 5:20pm (CST)
Duration: 26.64s, Total samples = 40.19s (150.86%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 24110ms, 59.99% of 40190ms total
Dropped 184 nodes (cum <= 200.95ms)
Showing top 10 nodes out of 114
flat flat% sum% cum cum%
9020ms 22.44% 22.44% 15990ms 39.79% runtime.scanobject
4510ms 11.22% 33.67% 4510ms 11.22% runtime.heapBitsForObject
3060ms 7.61% 41.28% 4580ms 11.40% runtime.mallocgc
2530ms 6.30% 47.57% 2550ms 6.34% runtime.greyobject
1110ms 2.76% 50.34% 1110ms 2.76% runtime.memclrNoHeapPointers
1080ms 2.69% 53.02% 1080ms 2.69% runtime.heapBitsSetType
1010ms 2.51% 55.54% 1670ms 4.16% runtime.mapaccess2
640ms 1.59% 57.13% 1080ms 2.69% runtime.mapassign_faststr
600ms 1.49% 58.62% 600ms 1.49% runtime.aeshashbody
550ms 1.37% 59.99% 1760ms 4.38% encoding/json.(*Decoder).readValue
(pprof)