11 Go测试

一、测试基础概述

Go语言内建一套基于表格驱动测试的机制,只需编写少量代码,就可对特定功能进行单元测试和基准测试(性能测试或压力测试)

二、单元测试

所谓单元测试,是程序中对最小功能单元进行检查和验证。

Go进行单元测试非常简单:

  • 创建一个go文件,在命名文件时以_test.go结尾,例如: unit_test.go

  • 每个测试文件可支持多个测试用例的编写,每个测试用例函数需要以Test为前缀,且其只有一个*testing.T指针类型的入参。例如:

func TestAXXX( t *testing.T ){}

func TestBXXX( t *testing.T ){}
  • 运行go test -v {testing_filename.go} 执行测试并打印测试详细数据
go test 指定文件时默认执行文件内的所有测试用例。可以使用-run参数选择需要的测试用例单独执行
以下对一个斐波那契数列计算函数的测试示例

测试目标功能函数:

//递归实现的斐波那契数列
func Fibonacci(n int) int {
    rs := 0
    //归
    if n == 1 || n == 2 {
        return 1
    }
    //递
    rs = Fibonacci(n-1) + Fibonacci(n-2)
    return rs
}

单元测试用例:

import "testing"

func TestFibonacci(t *testing.T) {
    //先编一个预定义的参数和结果集
    caseMap := make(map[int]int)
    caseMap[1] = 1
    caseMap[3] = 2
    caseMap[5] = 5
    caseMap[10] = 55

    //测试运行一个函数,得到结果
    for i, v := range caseMap {
        rs := Fibonacci(i) //目标测试函数
        if rs != v {
            t.Fatalf("测试用例发现错误:参数%d,预计得到结果%d,实际得到结果%d", i, v, rs)
        }
    }

    t.Log("测试结束,没有发现问题。")

}

//OUTPUT SUCCESSFUL:
=== RUN   TestFibonacci
--- PASS: TestFibonacci (0.00s)
    TestUnit_test.go:48: 测试结束,没有发现问题。
PASS
ok      command-line-arguments  0.005s

//OUTPUT FAIL:
//为模拟测试失败,更改一个预定义结果集让其不通过
=== RUN   TestFibonacci
--- FAIL: TestFibonacci (0.00s)
    TestUnit_test.go:44: 测试用例发现错误:参数10,预计得到结果50,实际得到结果55
FAIL
FAIL    command-line-arguments  0.005s


//注意:在使用go test 测试单个文件的时候,本人运行出现报错:
# command-line-arguments [command-line-arguments.test]
Code/go/src/mydemo/base/TestUnit_test.go:42:9: undefined: Fibonacci
FAIL    command-line-arguments [build failed]

//可看到对测试目标运行测试时没有发现目标函数,看到后面[build failed]失败了,这里需要把测试目标的文件也添加到命令:
go test -v {testing_filename.go} {target_filename.go}

以上为最简单的功能测试实现,可以发现主要为testing包的支持,那么testing.T这个类型可以提供哪些测试功能呢?下面解析一下:

    //标记失败但继续运行该测试
    t.Fail()
    //标记失败并立刻终止该测试
    t.FailNow()

    //打印测试日志
    t.Log("日志记录...")
    t.Logf("[Error]:%s/n","错误原因")

    //t.Log() + t.Fail()
    t.Error("劳资是日志")
    
    //t.Logf() + t.Fail()
    t.Errorf("[Error]:%s/n","错误原因")

    //t.Log() + t.FailNow()
    t.Fatal("日志记录...")
    
    //t.Logf() + t.FailNow()
    t.Fatalf("[Error]:%s/n","错误原因")

以上可看到主要是测试运行时的日志记录和错误处理功能

三、基准测试

基准测试可以测试一段程序的运行性能及耗费 CPU 的程度。Go 语言中提供了基准测试框架,使用方法类似于单元测试,使用者无须准备高精度的计时器和各种分析工具,基准测试本身即可以打印出非常标准的测试报告。

Go进行基准测试和单元测试一样简单:

  • 创建一个go文件,在命名文件时以_test.go结尾,例如: benchmark_test.go

  • 每个测试文件可支持多个测试用例的编写,每个测试用例函数需要以Benchmark为前缀,且其只有一个*testing.B指针类型的入参。例如:

func Benchmark_AXXX( t *testing.B ){}

func Benchmark_BXXX( t *testing.B ){}
  • 运行go test -v -bench=. benchmark_test.go 执行基准测试
-bench=.表示运行 benchmark_test.go 文件里的所有基准测试,和单元测试中的-run类似

基准测试用例:

func BenchmarkFibonacci(b *testing.B) {
    b.ReportAllocs() //内存开销
    //b.N为常规写法
    for i := 0; i < b.N; i++ {
        Fibonacci(10)
    }
}


//OUTPUT:
goos: darwin
goarch: amd64
BenchmarkFibonacci-4    10000000           204 ns/op           0 B/op          0 allocs/op
--- BENCH: BenchmarkFibonacci-4
PASS
ok      command-line-arguments  2.257s

//10000000 表示测试的次数,也就是 testing.B 结构中提供给程序使用的 N。“204 ns/op”表示每一个操作耗费多少时间(纳秒)。

基准测试原理:

基准测试框架对一个测试用例的默认测试时间是 1 秒。开始测试时,当以 Benchmark 开头的基准测试用例函数返回时还不到 1 秒,那么 testing.B 中的 N 值将按 1、2、5、10、20、50……递增,同时以递增后的值重新调用基准测试用例函数。

通过-benchtime参数可以自定义测试时间:

go test -v -bench=. -benchtime=5s benchmark_test.go
//OUTPUT:
goos: darwin
goarch: amd64
BenchmarkFibonacci-4    30000000           204 ns/op           0 B/op          0 allocs/op
--- BENCH: BenchmarkFibonacci-4
PASS
ok      command-line-arguments  6.337s

通过-benchmem参数以显示内存分配情况

go test -v -bench=BenchmarkFibonacci -benchmem benchmark_test.go
//OUTPUT:
goos: darwin
goarch: amd64
BenchmarkFibonacci-4    10000000           203 ns/op           0 B/op          0 allocs/op
--- BENCH: BenchmarkFibonacci-4
PASS
ok      command-line-arguments  2.251s
//输出差不多,这个斐波那契例子内存分配几乎忽略不计

控制计时器

有些测试需要一定的启动和初始化时间,如果从 Benchmark() 函数开始计时会很大程度上影响测试结果的精准性。testing.B 提供了一系列的方法可以方便地控制计时器,从而让计时器只在需要的区间进行测试。

示例:

func Benchmark_Add_TimerControl(b *testing.B) {
    // 重置计时器
    b.ResetTimer()
    // 停止计时器
    b.StopTimer()
    // 开始计时器
    b.StartTimer()
    var n int
    for i := 0; i < b.N; i++ {
        n++
    }
}

四、性能分析

go提供两种pprof包来做代码的性能分析

  • runtime/pprof : 基本性能分析包
  • net/http/pprof : 基于runtime/pprof封装,并在http端口暴露

1. pprof是什么?

pprof是Go提供的可视化性能分析工具,在性能测试中读取分析样本的集合,并生成报告以可视化并帮助分析数据。pprof既能生成报告文件,也可以借助graphviz生成web界面。

你能看到什么?

  • CPU Profiling:CPU 分析,按照一定的频率采集所监听的应用程序 CPU(含寄存器)的使用情况,可确定应用程序在主动消耗 CPU 周期时花费时间的位置
  • Memory Profiling:内存分析,在应用程序进行堆分配时记录堆栈跟踪,用于监视当前和历史内存使用情况,以及检查内存泄漏
  • Block Profiling:阻塞分析,记录 goroutine 阻塞等待同步(包括定时器通道)的位置
  • Mutex Profiling:互斥锁分析,报告互斥锁的竞争情况

2.pprof使用方式

2.1 基于基准测试生成性能分析文件:
go test -bench=. -benchtime="3s" -cpuprofile=profile_cpu.out

运行完成后,会发现在当前目录生成两个文件

profile_cpu.out —— 分析报告文件

base.test —— 可执行程序

2.2 查看性能分析报告
  • 终端查看
//使用go提供的工具在终端查看
go tool pprof base.test profile_cpu.out

//进入终端pprof查看模式
File: base.test
Type: cpu
Time: Jul 4, 2019 at 11:21am (CST)
Duration: 6.55s, Total samples = 6.02s (91.95%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

//输入help查看命令集合,可见还是功能还是很强大的。
(pprof) help
  Commands:
    callgrind        Outputs a graph in callgrind format
    comments         Output all profile comments
    disasm           Output assembly listings annotated with samples
    dot              Outputs a graph in DOT format
    eog              Visualize graph through eog
    evince           Visualize graph through evince
    gif              Outputs a graph image in GIF format
    gv               Visualize graph through gv
    kcachegrind      Visualize report in KCachegrind
    list             Output annotated source for functions matching regexp
    pdf              Outputs a graph in PDF format
    peek             Output callers/callees of functions matching regexp
    png              Outputs a graph image in PNG format
    proto            Outputs the profile in compressed protobuf format
    ps               Outputs a graph in PS format
    raw              Outputs a text representation of the raw profile
    svg              Outputs a graph in SVG format
    tags             Outputs all tags in the profile
    text             Outputs top entries in text form
    top              Outputs top entries in text form
    topproto         Outputs top entries in compressed protobuf format
    traces           Outputs all profile samples in text form
    tree             Outputs a text rendering of call graph
    web              Visualize graph through web browser
    weblist          Display annotated source in a web browser
    o/options        List options and their current values
    quit/exit/^D     Exit pprof
    
...

//使用top 5查看前5个耗cpu的调用
(pprof) top 5
Showing nodes accounting for 5.99s, 99.50% of 6.02s total
Dropped 5 nodes (cum <= 0.03s)
      flat  flat%   sum%        cum   cum%
     5.94s 98.67% 98.67%      5.99s 99.50%  command-line-arguments.Fibonacci
     0.05s  0.83% 99.50%      0.05s  0.83%  runtime.newstack
         0     0% 99.50%      5.99s 99.50%  command-line-arguments.BenchmarkFibonacci
         0     0% 99.50%      5.99s 99.50%  testing.(*B).launch
         0     0% 99.50%      5.99s 99.50%  testing.(*B).runN

...
  • web查看
//先安装[graphviz](http://www.graphviz.org/download/)
//加上--web
go tool pprof --web base.test profile_cpu.out

//此方式会生成一个.svg格式的文件,可以用任何支持.svg的软件打开
  • pdf输出
//加上-pdf选项,并把数据重定向到新的pdf格式文件即可
go tool pprof base.test -pdf profile_cpu.out > profile_cpu.pdf

3. 服务版的pprof性能分析

要使用服务版的pprof,只需在启动服务的main中引入相关包,启动服务即可。

    "net/http"
    _ "net/http/pprof"

运行服务时,你的 HTTP 服务会多出 /debug/pprof 这个访问路径,用于查看服务器版的性能分析报告,例如:访问http://{hostname}:{port}/debug/pprof/

可以通过访问各自类型的性能分析页面了解服务的总体情况:
  • cpu: http://{hostname}:{port}/debug/pprof/profile,默认进行 30s 的 CPU Profiling,得到一个分析用的 profile 文件
  • block:http://{hostname}:{port}/debug/pprof/block,查看导致阻塞同步的堆栈跟踪
  • goroutine:http://{hostname}:{port}/debug/pprof/goroutine,查看当前所有运行的 goroutines 堆栈跟踪
  • heap: http://{hostname}:{port}/debug/pprof/heap,查看活动对象的内存分配情况
  • mutex:http://{hostname}:{port}/debug/pprof/mutex,查看导致互斥锁的竞争持有者的堆栈跟踪
  • threadcreate:http://{hostname}:{port}/debug/pprof/threadcreate,查看创建新OS线程的堆栈跟踪
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容