Skip to content

第八章 测试与调优 更新ing ​

第一部分 测试 ​

一、Golang测试框架 ​

Go语言提供了内置的测试框架,使得编写和运行单元测试变得非常简单和高效

1.基本概念 ​

测试文件:

  • 测试文件:所有文件以_test.go结尾
  • 单元测试函数:函数名称必须以Test开头,函数签名写为func TestXxx(t *testing.T)
  • 基准测试函数:函数名称必须以Benchmark开头,函数签名写为func BenchmarkXxx(b *testing.B)

测试包:

  • testing包:提供了编写测试所需的基本功能
  • testing.T类型:用于管理单元测试状态和支持格式化测试日志
  • testing.B类型:用于管理基准测试状态和支持格式化基准测试日志
  • testing.M类型:用于管理测试,提供了运行所有测试和获取测试结果的方法

2.TestMain ​

一个特殊的函数,用于在运行所有测试之前和之后执行一些设置和清理操作,函数签名写为func TestMain(m *testing.M)

TestMain 函数通常用于以下场景:

  1. 设置全局状态:在所有测试运行之前进行一些全局状态的初始化。
  2. 清理操作:在所有测试运行之后进行一些清理操作。
  3. 自定义测试运行逻辑:可以在运行测试之前或之后执行一些自定义逻辑。
go
func TestMain(m *testing.M) {
    fmt.Println("Setup before running tests")   // 在运行测试之前执行的初始化操作
    exitCode := m.Run()                         // 运行所有测试
    fmt.Println("Cleanup after running tests")  // 在运行测试之后执行的清理操作
    os.Exit(exitCode)                           // 使用测试的退出码退出
}

3.运行测试 ​

bash
go test
bash
go test -bench=.
bash
go test -run ^Test.*Case$               # 指定运行符合正则表达式函数名的函数
go test test                            # 指定测试文件目录运行

4.接口测试 ​

5.测试覆盖率 ​

bash
go test -cover
bash
go test -coverprofile=coverage.out                        # 运行测试并生成一个详细的覆盖率报告文件
go test -coverprofile=coverage.out -coverpkg=./pkg/...    # 指定生成一个详细的覆盖率报告文件,并指定检测覆盖率的代码目录
go tool cover -func=coverage.out                          # 在命令行中查看覆盖率摘要
go tool cover -html=coverage.out                          # 在HTML中查看覆盖率摘要
go tool cover -html=coverage.out -o coverage.html         # 在HTML中查看覆盖率摘要并保存指定文件

二、单元测试 ​

testing.T 是用于编写单元测试的类型。它提供了一些方法来记录测试失败、跳过测试以及输出日志信息。每个测试函数都会接收一个 *testing.T 类型的参数。

go
func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5
    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}
常用方法
方法名作用
Error(args ...interface{})输出错误信息并标记测试失败,但继续执行测试
Errorf(format string, args ...interface{})格式化输出错误信息并标记测试失败,但继续执行测试
Fail()标记测试失败并立即终止测试
Fatal(args ...interface{})格式化输出错误信息并标记测试失败,立即终止测试
Log(args ...interface{})输出日志信息
Logf(format string, args ...interface{})格式化输出日志信息
Skip(args ...interface{})输出跳过测试的信息并标记测试为跳过
SkipNow()标记测试为跳过并立即终止测试
Skipf(format string, args ...interface{})格式化输出跳过测试的信息并标记测试为跳过

三、基准测试 ​

testing.B 是用于编写基准测试的类型。它提供了一些方法来控制基准测试的执行次数和记录基准测试的结果。每个基准测试函数都会接收一个 *testing.B 类型的参数。

go
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}
常用方法
方法名作用
func (b *B) Error(args ...interface{})输出错误信息并标记基准测试失败,但继续执行基准测试
func (b *B) Errorf(format string, args ...interface{})格式化输出错误信息并标记基准测试失败,但继续执行基准测试
func (b *B) Fail()标记基准测试失败,但继续执行基准测试
func (b *B) FailNow()标记基准测试失败并立即终止基准测试
func (b *B) Fatal(args ...interface{})输出错误信息并标记基准测试失败,立即终止基准测试
func (b *B) Fatalf(format string, args ...interface{})格式化输出错误信息并标记基准测试失败,立即终止基准测试
func (b *B) Log(args ...interface{})输出日志信息
func (b *B) Logf(format string, args ...interface{})格式化输出日志信息
func (b *B) ResetTimer()重置计时器
func (b *B) StartTimer()开始计时
func (b *B) StopTimer()停止计时
func (b *B) SetBytes(n int64)设置每次操作处理的字节数,用于计算每秒处理的字节数

四、测试与性能优化补充 ​

1.单元测试命令 ​

执行单元测试命令:go test x_test.go x.go

2.Mock ​

使用 monkey 包:github.com/bouk/monkey。

作用:使测试不依赖本地文件。

3.测试覆盖率补充 ​

意义:代表测试代码覆盖(执行)了原始代码的百分比。

计算覆盖率:go test x_test.go x.go --cover //直接运行测试并进行覆盖率计算

注:程序测试中一般覆盖率要求达到50%~60%,较高要求的情况下覆盖80%+。

4.字符串处理 ​

可以使用 string.Builder / bytes.Buffer 提升性能(前者性能更好)。

5.atomic包 ​

atomic 包可以通过硬件实现原子操作,效率比通过操作系统实现的 sync.Mutex 高。并发场景下的具体用法见第五章并发编程。

6.基准测试命令补充 ​

执行基准测试命令:go test -bench=. -benchmem。

第二部分 调试与性能优化 ​

一、性能分析工具(pprof) ​

Go语言内置的性能分析工具,用于分析Go程序的CPU使用情况、内存分配、阻塞操作等。

1.基本使用 ​

使用该工具需要在程序的初始化部分导入pprof包

go
import _ "net/http/pprof"

也可以同时引入 net/http,并在 main 函数中启动监听 6060 端口的 HTTP 服务:

go
"net/http"        // 需要 import
_ "net/http/pprof" // 需要 import
go func() {        // 添加在 main 函数中
    if err := http.ListenAndServe(":6060", nil); err != nil {
        log.Fatal(err)
    }
    os.Exit(0)
}()

为了能够访问pprof提供的分析数据,需要启动一个HTTP服务器

go
package main

import (
    "log"
    "net/http"
    _ "net/http/pprof"
)

func main() {
    go func() {
        if err := http.ListenAndServe(":6060", nil); err != nil {
            log.Fatal(err)
        }
    }()

    // 程序逻辑
    
    select {}  // 保持程序运行
}

2.性能分析 ​

启动程序后,可以在浏览器中访问以下 URL 来查看不同类型的分析数据

  1. http://127.0.0.1:6060/debug/pprof/:pprof的概览页面。
  2. http://127.0.0.1:6060/debug/pprof/goroutine:当前所有goroutine的堆栈跟踪。
  3. http://127.0.0.1:6060/debug/pprof/heap:内存分配情况。
  4. http://127.0.0.1:6060/debug/pprof/threadcreate:线程创建情况。
  5. http://127.0.0.1:6060/debug/pprof/block:阻塞操作情况。
  6. http://127.0.0.1:6060/debug/pprof/profile:CPU 分析数据(默认 30 秒)。

3.详细分析 ​

a.采样 ​

在程序运行过程中,可以通过curl命令将分析文件保存到本地

bash
curl http://localhost:6060/debug/pprof/profile?seconds=30 > cpu.prof
bash
curl http://localhost:6060/debug/pprof/heap > heap.prof
bash
curl http://localhost:6060/debug/pprof/goroutine > goroutine.prof
bash
curl http://localhost:6060/debug/pprof/mutex > mutex.prof
bash
curl http://localhost:6060/debug/pprof/block > block.prof
b.分析 ​

在捕获分析文件后,使用 go tool pprof 指令进入交互界面分析

bash
go tool pprof xxx.prof
c.分析指令 ​

具体可用指令:

  • top: 显示占用系统最多的函数
  • list [regex]: (根据正则表达式)显示特定函数详细信息
  • web: (需要Graphviz支持)生产图形化报告并在浏览器打开

在命令行中也可以直接使用以下方式查看 CPU 采样:

go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"

top 指令中,flat == cum 表示函数中没有调用其他函数;flat = 0 表示函数中只有其他函数的调用。list [正则表达式] 可以根据指定的正则表达式查找函数。

如果执行 web 时出现 failed to execute dot. Is Graphviz installed? Error: exec: "dot"...,需要下载 Graphviz,并将 bin 目录配置到环境变量 PATH 中。

top:指令相关指标与含义

指标含义
flat当前函数本身的执行耗时
flat%flat占CPU总时间的比例
sum%上面每一行的flat%的总和
cum当前函数加上其调用函数的总耗时
cum%cum占CPU总时间的比例
d.网页分析 ​

在程序运行过程中,直接通过以下命令即可打开网页进行分析(需要Graphviz支持)

bash
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/profile?seconds=10"
bash
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"
bash
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"
bash
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
bash
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"

二、trace(并发调度追踪) ​

三、日志工具(zap/logrus,结构化日志) ​