Go语言入门22:单元测试与基准测试完全详解(单元测试规范、覆盖率、Mock、Benchmark性能压测、工程标准化)

Go语言入门22:单元测试与基准测试完全详解(单元测试规范、覆盖率、Mock、Benchmark性能压测、工程标准化)

一、前言(本篇学习目标)

到目前为止,我们写的代码都是「手动运行、肉眼看结果」。在项目体量小的时候没问题,一旦项目模块变多、迭代变快、多人协作,手动测试完全不可用

Go 语言原生内置testing 测试标准库,无需第三方框架,开箱即用,支持:

  • 单元测试:验证函数逻辑正确性,回归防错

  • 覆盖率测试:检测代码是否被测到

  • 基准测试(Benchmark):量化函数性能、对比优化

  • 示例测试(Example):生成文档+可运行示例

测试是工程化、线上稳定、重构安全的核心能力,也是面试高频考点。本篇带你从零吃透 Go 整套测试体系,写出企业级标准测试代码。

本篇学习目标:

  1. 掌握 Go 测试文件命名、函数命名、执行规范

  2. 熟练编写标准单元测试、断言、错误校验、用例设计

  3. 学会测试覆盖率查看、精准补全测试用例

  4. 掌握 Benchmark 基准测试,分析性能、对比代码优劣

  5. 理解基准测试原理、计时器控制、内存分配分析

  6. 掌握子测试、表格驱动测试(企业主流写法)

  7. 学会简单 Mock 测试、规避文件/网络依赖

  8. 掌握工程测试最佳实践、避坑规范

二、Go 测试核心规范(必须熟记)

1. 测试文件规则

  • 测试文件后缀固定:_test.go

  • 测试文件和源码文件同目录、同包名

  • 测试文件不会被编译进最终二进制程序,只用于测试

2. 三类测试函数规则

类型

函数前缀

入参

用途

单元测试

TestXxx

*testing.T

校验逻辑对错

基准测试

BenchmarkXxx

*testing.B

压测、统计性能

示例测试

ExampleXxx

文档示例、自动校验输出

三、第一个单元测试(上手实战)

1. 编写待测试源码

新建 calc.go,写一个简单业务函数。

package demo

import "errors"

// Add 加法
func Add(a, b int) int {
    return a + b
}

// Div 除法
func Div(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("除数不能为0")
    }
    return a / b, nil
}

2. 编写单元测试代码

新建 calc_test.go,编写标准测试函数。

package demo

import (
    "testing"
)

// TestAdd 测试加法
func TestAdd(t *testing.T) {
    res := Add(1, 2)
    if res != 3 {
        // 测试失败,打印日志并终止当前测试
        t.Fatalf("Add(1,2) 预期3,实际%d", res)
    }
}

// TestDiv 测试除法
func TestDiv(t *testing.T) {
    // 正常用例
    res, err := Div(10, 2)
    if err != nil {
        t.Fatalf("Div(10,2) 不应报错,err:%v", err)
    }
    if res != 5 {
        t.Fatalf("Div(10,2) 预期5,实际%d", res)
    }

    // 异常用例:除数为0
    _, err = Div(10, 0)
    if err == nil {
        t.Fatal("Div(10,0) 应该报错,实际无错误")
    }
}

3. 常用测试方法

  • t.Log():打印测试日志,测试成功默认不显示

  • t.Error():标记失败,继续执行

  • t.Fatal():标记失败,终止当前测试函数

4. 执行测试命令

终端进入当前目录:

# 执行当前包所有测试
go test

# 详细输出日志
go test -v

四、企业级标准:表格驱动测试(重点)

真实开发不会一个场景写一段判断,全部使用表格驱动测试:批量定义用例、批量执行、统一断言,代码极简、可无限扩展。

package demo

import (
    "testing"
)

func TestDiv_Table(t *testing.T) {
    // 批量测试用例
    cases := []struct {
        name    string
        a, b    int
        wantRes int
        wantErr bool
    }{
        {name: "正常整除", a: 10, b: 2, wantRes: 5, wantErr: false},
        {name: "除数为0报错", a: 10, b: 0, wantRes: 0, wantErr: true},
        {name: "负数除法", a: -8, b: 2, wantRes: -4, wantErr: false},
    }

    // 遍历执行所有用例
    for _, c := range cases {
        // 子测试:每个用例独立命名、独立结果
        t.Run(c.name, func(t *testing.T) {
            res, err := Div(c.a, c.b)
            // 错误断言
            if (err != nil) != c.wantErr {
                t.Fatalf("err 预期%v,实际%v", c.wantErr, err)
            }
            // 结果断言
            if !c.wantErr && res != c.wantRes {
                t.Fatalf("res 预期%d,实际%d", c.wantRes, res)
            }
        })
    }
}

优势:新增测试场景只需追加 case,不用改逻辑,是大厂 Go 测试强制规范

五、测试覆盖率(检测代码是否测全)

覆盖率可以直观看到:哪些代码没被测试覆盖、存在隐患

1. 查看覆盖率概要

go test -cover

2. 生成可视化覆盖率报告(超实用)

# 生成覆盖率数据文件
go test -coverprofile=cover.out

# 打开浏览器可视化查看代码覆盖情况
go tool cover -html=cover.out

绿色 = 已覆盖,红色 = 未覆盖,精准定位漏测代码。

六、基准测试 Benchmark(性能压测核心)

1. 基准测试规则

  • 函数名:BenchmarkXxx

  • 入参:*testing.B

  • 必须循环 b.N 次(框架自动调整次数)

  • 默认不执行,必须加参数 -bench

2. 基础基准测试实战

package demo

import (
    "testing"
)

// BenchmarkAdd 压测加法函数
func BenchmarkAdd(b *testing.B) {
    // 重置计时器,跳过初始化耗时
    b.ResetTimer()

    // b.N 由测试框架自动赋值
    for i := 0; i < b.N; i++ {
        Add(123, 456)
    }
}

3. 执行压测命令

# 执行所有压测
go test -bench=.

# 同时查看内存分配
go test -bench=. -benchmem

4. 结果指标解读(面试必考)

输出示例:100000000 12.3 ns/op 0 B/op 0 allocs/op

  • 100000000:总执行次数

  • 12.3 ns/op:单次执行耗时(越小越快)

  • 0 B/op:单次内存分配大小

  • 0 allocs/op:单次堆内存分配次数(越少性能越好)

5. 精准压测:屏蔽初始化耗时

压测中如果有前置初始化代码,需要用 b.StopTimer() / b.StartTimer() 精准控制计时区间。

func BenchmarkAdd_Init(b *testing.B) {
    // 前置初始化(不计入耗时)
    a, bVal := 100, 200

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        Add(a, bVal)
    }
}

七、实战:性能对比优化(真实开发场景)

通过 Benchmark 量化对比两种写法性能,告别主观感觉优化。

示例:字符串拼接 + 与 strings.Builder 性能对比

package demo

import (
    "strings"
    "testing"
)

// 普通加号拼接
func BenchmarkStringAdd(b *testing.B) {
    s := ""
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        s += "hello"
    }
    _ = s
}

// Builder 缓冲拼接
func BenchmarkStringBuilder(b *testing.B) {
    var builder strings.Builder
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        builder.WriteString("hello")
    }
    _ = builder.String()
}

压测结果会直观体现:strings.Builder 性能碾压加号拼接,内存分配极少。

八、简单 Mock 测试(解除文件/网络依赖)

单元测试原则:不能依赖外部文件、数据库、网络,必须纯内存、可随时跑。

通过接口抽象实现 Mock 替换,是工程测试核心思想。

package demo

import "testing"

// 抽象接口
type DataFetcher interface {
    GetData() string
}

// 真实业务结构体
type RealData struct{}
func (r *RealData) GetData() string {
    // 实际会读文件/查数据库
    return "real data"
}

// Mock 测试结构体
type MockData struct{}
func (m *MockData) GetData() string {
    return "mock data"
}

// 业务函数(依赖接口,不依赖具体实现)
func BusinessLogic(f DataFetcher) string {
    return f.GetData()
}

// 单元测试使用Mock
func TestBusinessLogic(t *testing.T) {
    mock := &MockData{}
    res := BusinessLogic(mock)
    if res != "mock data" {
        t.Fatal("mock 数据不匹配")
    }
}

核心思想:依赖抽象、不依赖实现,测试时替换 Mock,彻底解耦外部依赖。

九、工程测试最佳实践与避坑

  • 单元测试必须:无依赖、可重复、秒执行、无副作用

  • 所有业务函数优先使用「表格驱动+子测试」写法

  • 禁止测试函数写死大量逻辑,用例与逻辑分离

  • 基准测试必须屏蔽初始化耗时,保证数据精准

  • 性能优化必须用 Benchmark 量化,不凭感觉

  • CI/CD 流水线必须开启测试+覆盖率校验,防止坏代码提交

十、本篇核心总结

1. Go 测试三剑客:Test单元测试、Benchmark基准测试、Example示例测试

2. 企业标准写法:表格驱动测试 + 子测试,用例清晰、易维护;

3. 覆盖率工具可精准排查漏测代码,保障迭代质量;

4. Benchmark 可量化耗时、内存分配,是性能优化的核心依据;

5. 测试解耦核心:接口抽象+Mock,杜绝外部依赖,实现纯内存快速测试。

十一、下期预告

下一篇:Go 模块管理与工程化项目规范,精讲 go mod、依赖管理、私有库、版本控制、项目目录规范,彻底告别混乱项目结构!

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容