go官方文档
go语言参考
在写过很多go代码之后,感觉自己并没有完全掌握go语言,还有很多知识盲区,所以有了这个go学习笔记系列,本系列是作者跟着电子书重新复习go语言相关内容的笔记
go-zero微服务实战系列(十一、大结局)
github gomock
gomock中文示例
csdn 超级强zcq
李文周博客
Go 检查和报告错误条件的惯有方式
- 产生错误的函数会返回两个变量,一个值和一个错误码;如果后者是 nil 就是成功,非 nil 就是发生了错误。
- 为了防止发生错误时正在执行的函数(如果有必要的话甚至会是整个程序)被中止,在调用函数后必须检查错误。
错误处理
1
2
3
|
type error interface {
Error() string
}
|
当程序处于错误状态时可以用 os.Exit(1) 来中止运行。
定义错误
1
|
err := errors.New(“math - square root of negative number”)
|
每个包中有自己定义的error
用 fmt 创建错误对象
1
|
err = fmt.Errorf("usage: %s infile.txt outfile.txt", filepath.Base(os.Args[0]))
|
运行时异常和 panic
1
|
panic("A severe error occurred: stopping the program!")
|
当你调用panic函数时,Go运行时会立即停止当前函数的执行,运行任何已经被defer关键字注册的函数,然后向上返回到调用者。这个过程会一直持续,直到整个调用链被返回,导致程序终止
panic并不是通过操作系统的信号机制来实现的。相反,它是通过Go的运行时系统直接实现的
从 panic 中恢复(Recover)
必须为defer语句或者是defer语句调用的匿名函数,不能在defer块里面调用其他函数来recover
1
2
3
4
5
|
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panicing %s\r\n", e)
}
}()
|
如果一个Go语言程序中的panic没有被recover捕获,会发生以下几件事情:
- 运行时会输出panic的值,以及在panic发生时的堆栈信息。
- 运行时会将所有的goroutine关闭。这个过程中,如果在goroutine中使用了defer关键字注册的函数,那么这些函数会被调用。
- 最后,程序将以非零的退出码退出。这个退出码通常是1,表明程序异常终止。
启动外部命令和程序
os 包有一个 StartProcess 函数可以调用或启动外部系统命令和二进制可执行文件;它的第一个参数是要运行的进程,第二个参数用来传递选项或参数,第三个参数是含有系统环境基本信息的结构体。这个在windows里面用不了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/* Linux: */
env := os.Environ()
procAttr := &os.ProcAttr{
Env: env,
Files: []*os.File{
os.Stdin,
os.Stdout,
os.Stderr,
},
}
// 1st example: list files
pid, err := os.StartProcess("/bin/ls", []string{"ls", "-l"}, procAttr)
if err != nil {
fmt.Printf("Error %v starting process!", err) //
os.Exit(1)
}
|
exec 包中也有同样功能的更简单的结构体和函数;主要是 exec.Command(name string, arg …string) 和 Run()
1
2
3
4
5
6
|
cmd := exec.Command("gedit") // this opens a gedit-window
err = cmd.Run()
if err != nil {
fmt.Printf("Error %v executing command!", err)
os.Exit(1)
}
|
Go 中的单元测试和基准测试
_test 程序不会被普通的 Go 编译器编译,所以当放应用部署到生产环境时它们不会被部署;只有 gotest 会编译所有的程序:普通程序和测试程序。
测试函数必须有这种形式的头部:
1
|
func TestAbcde(t *testing.T)
|
T 是传给测试函数的结构类型,用来管理测试状态,支持格式化测试日志,如 t.Log,t.Error,t.ErrorF 等。在函数的结尾把输出跟想要的结果对比,如果不等就打印一个错误。成功的测试则直接返回。
用下面这些函数来通知测试失败:
- func (t *T) Fail()标记测试函数为失败,然后继续执行(剩下的测试)。
- func (t *T) FailNow()标记测试函数为失败并中止执行;文件中别的测试也被略过,继续执行下一个文件。
- func (t *T) Log(args …interface{}) args 被用默认的格式格式化并打印到错误日志中。
- func (t *T) Fatal(args …interface{}) 结合 先执行 3),然后执行 2)的效果。
–chatty 或 -v 选项,每个执行的测试函数以及测试状态会被打印。
testing 包中有一些类型和函数可以用来做简单的基准测试;测试代码中必须包含以 BenchmarkZzz 打头的函数并接收一个 *testing.B 类型的参数
1
2
3
4
5
|
func BenchmarkReverse(b *testing.B) {
for n := 0; n < b.N; n++ {
testfunc(xxx)
}
}
|
命令 go test –-bench=.* 会运行所有的基准测试函数;代码中的函数会被调用 N 次(N是非常大的数,如 N = 1000000),并展示 N 的值和函数执行的平均时间,单位为 ns(纳秒,ns/op)。如果是用 testing.Benchmark 调用这些函数,直接运行程序即可
–bench对应的是一个正则表达式,匹配该正则表达式的基准测试函数才会执行(可以是子串匹配)
-benchmem 参数展示内存消耗情况
-cpu 使用几个cpu核心
-count 连续跑N次
-benchtime 指定运行秒数
b.ResetTimer()
方法可以重置基准测试定时器,可以用于跳过耗时的初始化操作
测试的具体例子
even/even.go:
1
2
3
4
5
6
7
8
9
|
package even
func Even(i int) bool { // Exported function
return i%2 == 0
}
func Odd(i int) bool { // Exported function
return i%2 != 0
}
|
even/oddeven_test.go:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package even
import "testing"
func TestEven(t *testing.T) {
if !Even(10) {
t.Log(" 10 must be even!")
t.Fail()
}
if Even(7) {
t.Log(" 7 is not even!")
t.Fail()
}
}
func TestOdd(t *testing.T) {
if !Odd(11) {
t.Log(" 11 must be odd!")
t.Fail()
}
if Odd(10) {
t.Log(" 10 is not odd!")
t.Fail()
}
}
|
由于测试需要具体的输入用例且不可能测试到所有的用例(非常像一个无穷的数),所以我们必须对要使用的测试用例思考再三。
至少应该包括:
- 正常的用例
- 反面的用例(错误的输入,如用负数或字母代替数字,没有输入等)
- 边界检查用例(如果参数的取值范围是 0 到 1000,检查 0 和 1000 的情况)
用(测试数据)表驱动测试
把测试的输入数据和期望的结果写在一起组成一个数据表:表中的每条记录都是一个含有输入和期望值的完整测试用例,还可以结合像测试名字这样的额外信息来让测试输出更多的信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
var tests = []struct{ // Test table
in string
out string
}{
{“in1”, “exp1”},
{“in2”, “exp2”},
{“in3”, “exp3”},
...
}
func TestFunction(t *testing.T) {
for i, tt := range tests {
s := FuncToBeTested(tt.in)
if s != tt.out {
t.Errorf(“%d. %q => %q, wanted: %q”, i, tt.in, s, tt.out)
}
}
}
|
如果大部分函数都可以写成这种形式,那么写一个帮助函数 verify 对实际测试会很有帮助:
1
2
3
4
5
|
func verify(t *testing.T, testnum int, testcase, input, output, expected string) {
if expected != output {
t.Errorf(“%d. %s with input = %s: output %s != %s”, testnum, testcase, input, output, expected)
}
}
|
TestFunction 则变为:
1
2
3
4
5
6
|
func TestFunction(t *testing.T) {
for i, tt := range tests {
s := FuncToBeTested(tt.in)
verify(t, i, “FuncToBeTested: “, tt.in, s, tt.out)
}
}
|
性能调试:分析并优化 Go 程序
时间和内存消耗
1
2
|
#!/bin/sh
/usr/bin/time -f '%Uu %Ss %er %MkB %C' "$@"
|
用 go test 调试
cpuprofile 和 -memprofile 标志向指定文件写入 CPU 或 内存使用情况报告
1
|
go test -x -v -cpuprofile=prof.out -file x_test.go
|
用 pprof 调试
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
func main() {
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
...
|
运行程序:progexec -cpuprofile=progexec.prof
使用gopprof工具查看:gopprof progexec progexec.prof
如果开启了 CPU 性能分析,Go 程序会以大约每秒 100 次的频率阻塞,并记录当前执行的 goroutine 栈上的程序计数器样本。
此工具一些有趣的命令:
- topN:展示分析结果中最频繁的 N 份样本,top5 它会展示在程序运行期间调用最频繁的 5 个函数
- web 或 web 函数名:生成一份 SVG 格式的分析数据图表,并在网络浏览器中打开它,函数被表示成不同的矩形(被调用越多,矩形越大),箭头指示函数调用链
- list 函数名 或 weblist 函数名,展示对应函数名的代码行列表,第 2 列表示当前行执行消耗的时间,这样就很好地指出了运行过程中消耗最大的代码。需要
pprof.WriteHeapProfile(f)
覆盖率测试
首先,我们要确保所有的测试都正常通过:
-coverprofile标志参数重新运行测试
1
2
|
$ go test -run=Coverage -coverprofile=c.out gopl.io/ch7/eval
ok gopl.io/ch7/eval 0.032s coverage: 68.5% of statements
|
这里的-run选项对应一个正则表达式,匹配的函数名才会进行测试
查看HTML报告
1
|
$ go tool cover -html=c.out
|
并行测试
T 和 B 的 Run 方法允许定义子单元测试和子基准测试,而不必为每个子测试和子基准定义单独的函数。
1
2
3
4
5
6
7
8
9
10
|
func TestGroupedParallel(t *testing.T) {
for _, tc := range tests {
tc := tc // capture range variable
t.Run(tc.Name, func(t *testing.T) {
//并行测试
t.Parallel()
...
})
}
}
|
并行测试加快测试效率。
gomock
基于interface{}的模拟工具
mockgen
mockgen工具用于根据go文件写好的interface生成相应的mock.go文件。
两种使用方式:
- mockgen -source=xxxx.go [other options]:源文件模式,将文件中所有interface都生成mock
- mockgen packagepath Interface1,Interface2…:反射模式,根据需要将包中的指定interface生成mock
常用选项
- -package: 指定mock类源文件的包名,默认位
mock_输入文件的包名
- -destination: mock类代码的输出文件,默认输出到标准输出,所以也可以使用重定向符号来输出到目标文件
使用方法
分4步
interface编写
infra/db.go
1
2
3
4
5
6
7
8
|
package db
type Repository interface {
Create(key string, value []byte) error
Retrieve(key string) ([]byte, error)
Update(key string, value []byte) error
Delete(key string) error
}
|
mock文件生成
1
|
mockgen -source=./infra/db.go -destination=./mock/mock_repository.go -package=mock
|
使用mock对象的文件
MySQL.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package MySQL
import "GoExample/GoMock/infra"
type MySQL struct {
DB db.Repository
}
func NewMySQL(db db.Repository) *MySQL {
return &MySQL{DB: db}
}
func (mysql *MySQL) CreateData(key string, value []byte) error {
return mysql.DB.Create(key, value)
}
func (mysql *MySQL) GetData(key string) ([]byte, error) {
return mysql.DB.Retrieve(key)
}
func (mysql *MySQL) DeleteData(key string) error {
return mysql.DB.Delete(key)
}
func (mysql *MySQL) UpdateData(key string, value []byte) error {
return mysql.DB.Update(key, value)
}
|
测试用例编写
- 导入mock相关包
1
2
3
4
5
|
import (
"testing"
"GoExample/GoMock/mock"
"github.com/golang/mock/gomock"
)
|
- mock控制器
mock控制器通过NewController接口生成,是mock生态系统的顶层控制,定义了mock对象的作用域和生命周期,以及mock对象的期望。当用例结束后,控制器会检查所有剩余期望的调用是否满足条件。
1
2
3
|
ctrl := NewController(t)
//1.5以上版本自动调用finish
defer ctrl.Finish()
|
mock对象创建时需要注入控制器
1
|
mockRepo := mock_db.NewMockRepository(ctrl)
|
- mock对象的行为注入
mock对象的行为注入,控制器通过map来维护,一个方法对应map的一项。一个方法在一个用例中可能调用多次,所以map的值类型是数组切片。当mock对象进行行为注入时,控制器会将行为Add。当该方法被调用时,控制器会将该行为Remove。
返回值
- Return():返回指定值
- Do(func):执行操作,忽略返回值
- DoAndReturn(func):执行并返回指定值
Any()函数表示对参数没有要求,其他的还有Equ()函数,其实直接将限制字面量放到参数也可以进行相等限制
1
2
3
|
mockRepo.EXPECT().Retrieve(Any()).Return(nil, ErrAny)
mockRepo.EXPECT().Create(Any(), Any()).Return(nil)
mockRepo.EXPECT().Retrieve(Any()).Return(objBytes, nil)
|
可以使用Times关键字来批量注入行为:
1
|
mockRepo.EXPECT().Create(Any(), Any()).Return(nil).Times(5)
|
调用次数
- Times() 断言 Mock 方法被调用的次数
- MaxTimes() 最大次数
- MinTimes() 最小次数
- AnyTimes() 任意次数(包括 0 次)
- 行为调用的保序
默认情况下,行为调用顺序可以和mock对象行为注入顺序不一致,即不保序
1
2
3
|
retrieveCall := mockRepo.EXPECT().Retrieve(Any()).Return(nil, ErrAny)
createCall := mockRepo.EXPECT().Create(Any(), Any()).Return(nil).After(retrieveCall)
mockRepo.EXPECT().Retrieve(Any()).Return(objBytes, nil).After(createCall)
|
- 通过InOrder函数来实现保序(底层仍然是使用after)
1
2
3
4
5
|
gomock.InOrder(
mockRepo.EXPECT().Retrieve(Any()).Return(nil, ErrAny)
mockRepo.EXPECT().Create(Any(), Any()).Return(nil)
mockRepo.EXPECT().Retrieve(Any()).Return(objBytes, nil)
)
|
底层原理
1
2
3
4
5
|
func InOrder(calls ...*Call) {
for i := 1; i < len(calls); i++ {
calls[i].After(calls[i-1])
}
}
|
- mock对象的注入
mock对象的行为都注入到控制器后,要将mock对象注入给interface,使得mock对象在测试中生效。
monkey
对函数和方法进行模拟,在运行时通过汇编语言重写可执行文件,将目标函数或方法的实现跳转到桩实现
- monkey不支持内联函数,在测试的时候需要通过命令行参数-gcflags=-l关闭Go语言的内联优化
- monkey不是线程安全的,所以不要把它用到并发的单元测试中
monkey的使用非常简单
对函数打桩
1
|
monkey.Patch(函数名, 函数定义)
|
main_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package main
import (
"bou.ke/monkey"
"fmt"
"testing"
)
func testFunc() {}
func TestAAA(t *testing.T) {
monkey.Patch(testFunc, func() {
fmt.Println("test success")
})
testFunc()
}
|
运行测试
1
|
go test main_test.go -run=AAA -v -gcflags=-l
|
对方法打桩
1
|
monkey.PatchInstanceMethod(reflect.Type,方法名,方法体)
|
main_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package main
import (
"bou.ke/monkey"
"fmt"
"reflect"
"testing"
)
type User struct {
Name string
Birthday string
}
func (*User) CalcAge() int { return 1 }
func TestAAA(t *testing.T) {
var u = &User{
Name: "q1mi",
Birthday: "1990-12-20",
}
monkey.PatchInstanceMethod(reflect.TypeOf(u), "CalcAge", func(*User) int {
fmt.Println("test success")
return 0
})
u.CalcAge()
}
|
运行测试
1
|
go test main_test.go -run=AAA -v -gcflags=-l
|
pprof性能分析
3种用法
- runtime/pprof:采集程序指定区块的运行数据,生成 profile.proto 文件并对其进行分析。
- net/http/pprof:基于 HTTP Server 运行,并且可以采集运行时数据进行分析。
- go test:通过运行测试用例,并指定所需标识来进行采集。
可采集信息有5种
- CPU Profiling:CPU 分析,按照一定的频率采集所监听的应用程序 CPU(含寄存器)的使用情况,可确定应用程序在主动消耗 CPU 周期时花费时间的位置。
- Memory Profiling:内存分析,在应用程序进行堆分配时记录堆栈跟踪,用于监视当前和历史内存使用情况,以及检查内存泄漏。
- Goroutine Profiling: Goroutine 分析,可以对当前应用程序正在运行的 Goroutine 进行堆栈跟踪和分析。
- Mutex Profiling:互斥锁分析,报告互斥锁的竞争情况。
- Block Profiling:阻塞分析,记录协程阻塞等待同步的情况。
net/http/pprof
启动
1
|
import _ "net/http/pprof"
|
引入时会自动给http默认server添加路由
你也可以自己添加路由
1
2
3
4
5
6
7
|
func init() {
http.HandleFunc("/debug/pprof/", Index)
http.HandleFunc("/debug/pprof/cmdline", Cmdline)
http.HandleFunc("/debug/pprof/profile", Profile)
http.HandleFunc("/debug/pprof/symbol", Symbol)
http.HandleFunc("/debug/pprof/trace", Trace)
}
|
访问有两种方式:
- 直接在线访问路由
浏览器在线访问路由
可以看到一些分析指标的数量以及链接,可以点击链接查看详情。这些分析指标对应的含义是:
- allocs:查看过去所有的内存分配信息。
- block:查看导致阻塞同步的堆栈跟踪。
- cmdline:当前程序的命令行的完整调用路径。
- goroutine:查看当前所有运行的 goroutines 堆栈跟踪。
- heap:查看活动对象的内存分配情况。
- mutex:查看导致互斥锁的竞争持有者的堆栈跟踪。
- profile:进行CPU分析,可以通过GET请求参数指定时间范围。
- threadcreate:查看创建新操作系统线程的堆栈跟踪。
- trace:生成对目前程序执行的分析文件,用于通过 go tool trace 进行分析,可以通过GET请求参数指定时间范围。
以及点击 full goroutine stack dump 将会列出各个协程详细的调用栈信息。
go tool的cli查看路由
1
|
go tool pprof <路由地址> [-http=:8081]
|
常用命令top、list、traces
- top:按指标大小列出前10个函数,比如内存是按内存占用多少,CPU是按执行时间多少
- list:查看某个函数的代码,以及该函数每行代码的指标信息,
list main.main
- traces:打印所有调用栈,以及调用栈的指标信息
- down到本地文件再访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
# CPU分析及运行分析网站
wget http://localhost:8080/debug/pprof/profile
go tool pprof -http=:8081 profile
# 内存占用分析及运行分析网站
wget http://localhost:8080/debug/pprof/heap
go tool pprof -http=:8081 heap
# 内存分配分析及运行分析网站
wget http://localhost:8080/debug/pprof/allocs
go tool pprof -http=:8081 allocs
# Goroutine分析及运行分析网站
wget http://localhost:8080/debug/pprof/goroutine
go tool pprof -http=:8081 goroutine
# 互斥锁分析及运行分析网站
wget http://localhost:8080/debug/pprof/mutex
go tool pprof -http=:8081 mutex
# 阻塞分析及运行分析网站
wget http://localhost:8080/debug/pprof/block
go tool pprof -http=:8081 block
# trace分析及运行分析网站
curl -o trace.out http://localhost:8080/debug/pprof/trace?seconds=5
go tool trace trace.out
|
runtime/pprof
生成pprof文件后用go tool打开
CPU分析
1
2
3
4
5
6
7
8
9
10
11
12
|
import (
"runtime/pprof"
)
func main() {
cpu_pprof, _ := os.Create("cpu.pprof")
_ = pprof.StartCPUProfile(cpu_pprof)
// do sth
pprof.StopCPUProfile()
_ = file.Close()
_ = cpu_pprof.Close()
}
|
内存分析
1
2
3
4
5
6
7
8
9
10
11
|
import (
"runtime/pprof"
)
func main() {
// do sth
runtime.GC()
heap_pprof, _ := os.Create("heap.pprof")
_ = pprof.WriteHeapProfile(heap_pprof)
_ = heap_pprof.Close()
}
|
go test 测试用例
1
2
|
go test -bench=. -cpuprofile=cpu.profile
go test -bench=. -memprofile=mem.profile
|
然后再用 go tool pprof查看并分析
1
2
|
go tool pprof cpu.profile
go tool pprof -http=:8081 cpu.profile
|
Go性能调优pprof
李文周博客
CSDN raoxiaoya
- CPU profile:报告程序的 CPU 使用情况,按照一定频率去采集应用程序在 CPU 和寄存器上面的数据
- Memory Profile(Heap Profile):报告程序的内存使用情况
- Block Profiling:报告 goroutines 不在运行状态的情况,可以用来分析和查找死锁等性能瓶颈
- Goroutine Profiling:报告 goroutines 的使用情况,有哪些 goroutine,它们的调用关系是怎样的
内置标准库:
- runtime/pprof:采集 工具型应用运行数据 进行分析
- net/http/pprof:采集 服务型应用运行时数据 进行分析
工具型应用
在应用退出的时候把 profiling 的报告保存到文件中,进行分析
CPU性能分析
开始记录
1
|
pprof.StartCPUProfile(w io.Writer)
|
停止记录
内存性能优化
记录堆栈信息
1
|
pprof.WriteHeapProfile(w io.Writer)
|
go tool pprof进行内存分析,默认-inuse_space选项,-inuse-objects选项查看分配对象数量
服务型应用
使用默认的http.DefaultServeMux:直接导入net/http/pprof自动帮你注册pprof路由
1
|
import _ "net/http/pprof"
|
自己的处理器Mux需要自己注册pprof路由
1
2
3
4
5
|
r.HandleFunc("/debug/pprof/", pprof.Index)
r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/debug/pprof/profile", pprof.Profile)
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
r.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
gin框架注册pprof路由:
运行应用后可以打开这些路由查看机器情况
- /debug/pprof/profile:访问这个链接会自动进行 CPU profiling,持续 30s,并生成一个文件供下载
- /debug/pprof/heap: Memory Profiling 的路径,访问这个链接会得到一个内存 Profiling 结果的文件
- /debug/pprof/block:block Profiling 的路径
- /debug/pprof/goroutines:运行的 goroutines 列表,以及调用关系
读取pprof文件,出现命令行交互界面
1
|
go tool pprof [binary] [source]
|
topN 命令查看程序中占用CPU前3的函数
list 函数名 命令查看具体的函数分析
web 命令调用 graphviz 生成svg图片,需要安装graphviz,
pdf 命令可以生成可视化的pdf文件
help 命令可以提供所有pprof支持的命令说明
go-torch和火焰图
下载go-torch:
1
|
go get -v github.com/uber/go-torch
|
自行下载perl
下载FlameGraph,并将目录添加到PATH:
1
|
git clone https://github.com/brendangregg/FlameGraph.git
|
windows需要修改go-torch/render/flamegraph.go的enerateFlameGraph
1
2
3
4
5
6
7
8
9
|
// GenerateFlameGraph runs the flamegraph script to generate a flame graph SVG. func GenerateFlameGraph(graphInput []byte, args ...string) ([]byte, error) {
flameGraph := findInPath(flameGraphScripts)
if flameGraph == "" {
return nil, errNoPerlScript
}
if runtime.GOOS == "windows" {
return runScript("perl", append([]string{flameGraph}, args...), graphInput)
}
return runScript(flameGraph, args, graphInput)
|
然后安装
1
|
go install github.com/uber/go-torch
|
压测工具wrk
1
|
go install github.com/adjust/go-wrk
|
使用go-torch
压测
1
|
go-wrk -n 50000 http://127.0.0.1:8080/book/list
|
生成火焰图
1
|
go-torch -u http://127.0.0.1:8080 -t 30
|
火焰图的y轴表示cpu调用方法的先后,x轴表示方法调用时间
火焰图分析内存性能数据
1
2
3
4
|
go-torch -inuse_space http://127.0.0.1:8080/debug/pprof/heap
go-torch -inuse_objects http://127.0.0.1:8080/debug/pprof/heap
go-torch -alloc_space http://127.0.0.1:8080/debug/pprof/heap
go-torch -alloc_objects http://127.0.0.1:8080/debug/pprof/heap
|
pprof与性能测试结合
go test 的-cpuprofile和-memprofile选项也会生成pprof文件
t.Skip
在 Go 中,testing
包提供了一个 T
类型,该类型有一个 Skip
方法,用于在测试函数内部标记该测试为跳过状态。当你希望临时跳过某些测试,但不希望完全删除或注释掉它们时,这是很有用的。
以下是如何使用 t.Skip
和相关方法的示例:
-
t.Skip
调用 t.Skip
会立即结束当前的测试,并将测试结果标记为跳过。
1
2
3
4
5
6
|
func TestExample(t *testing.T) {
if someCondition {
t.Skip("Skipping because of some condition")
}
// Test code here...
}
|
-
t.Skipf
与 t.Skip
类似,但允许你使用格式化字符串,类似于 fmt.Printf
。
1
2
3
4
5
6
|
func TestExample(t *testing.T) {
if someCondition {
t.Skipf("Skipping because value is %d", someValue)
}
// Test code here...
}
|
-
t.SkipNow
与 t.Skip
类似,但不需要参数。当你需要在没有消息的情况下跳过测试时使用。
1
2
3
4
5
6
|
func TestExample(t *testing.T) {
if someCondition {
t.SkipNow()
}
// Test code here...
}
|
需要注意的是,当你调用任何 t.Skip
相关的方法时,它将立即结束测试函数的执行,并且后续的代码不会运行。
此外,跳过的测试在执行 go test
时会在输出中标记为 SKIP
,以便于你能够轻松地识别哪些测试已被跳过。