Go从入门到精通(18)-代码测试

Go从入门到精通(18)

代码测试

文章目录

  • Go从入门到精通(18)
    • 代码测试
  • 前言
  • 单元测试(Unit Testing)
    • 基本规则
    • 示例代码
    • 常用断言方法
  • 基准测试(Benchmark Testing)
    • 基本规则
    • 示例代码
    • 基准测试结果分析
  • 表驱动测试与子测试
  • 测试覆盖率(Test Coverage)
  • 模拟(Mocking)与依赖注入
  • 基准测试进阶
  • 最佳实践
    • 单元测试
    • 基准测试
    • 测试组织
  • 总结


前言

Go 语言中,单元测试和基准测试是保证代码质量和性能的重要工具。Go 标准库提供了 testing 包,结合简单的命名约定,让测试变得简洁高效。


单元测试(Unit Testing)

基本规则

  • 文件命名:测试文件必须以 _test.go 结尾(如 main_test.go)。
  • 函数命名:测试函数必须以 Test 开头,参数为 t *testing.T。
  • 执行命令:go test 会自动运行当前目录下所有测试文件(在IDE可以单独执行某个测试函数)。

示例代码

// 待测试的函数(math.go)
func Add(a, b int) int {
    return a + b
}

// 测试函数(math_test.go)
package main

import "testing"

func TestAdd(t *testing.T) {
    //用(测试数据)表驱动测试
    tests := []struct {
        a, b int
        want int
    }{
        {1, 2, 3},
        {0, 0, 0},
        {-1, 1, 0},
    }

    for _, tt := range tests {
        t.Run(fmt.Sprintf("%d+%d", tt.a, tt.b), func(t *testing.T) {
            if got := Add(tt.a, tt.b); got != tt.want {
                t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
            }
        })
    }
}

常用断言方法

方法 用途 示例
t.Error(args…) 输出错误并继续测试 if x != y { t.Error(“x != y”) }
t.Fatal(args…) 输出错误并终止测试 if err != nil { t.Fatal(err) }
t.Errorf(format, …) 格式化输出错误 t.Errorf(“got %d, want %d”, got, want)
t.Run(name, fn) 子测试(并行执行) t.Run(“SubTest”, func(t *testing.T) { … })

基准测试(Benchmark Testing)

基本规则

  • 函数命名:基准测试函数必须以 Benchmark 开头,参数为 b *testing.B。
  • 循环执行:测试逻辑必须放在 b.N 循环中,Go 会自动调整 N 以获得准确结果。
  • 执行命令:go test -bench=. 运行基准测试。

示例代码

// 待测试的函数(strings.go)
func Reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

// 基准测试函数(strings_test.go)
package main

import "testing"

func BenchmarkReverse(b *testing.B) {
    s := "Hello, World!"
    for i := 0; i < b.N; i++ {
        Reverse(s)
    }
}

基准测试结果分析

执行 go test -bench=. 后,输出示例:

BenchmarkReverse-8 10000000 128 ns/op 48 B/op 2 allocs/op

  • BenchmarkReverse-8:测试函数名和 GOMAXPROCS 值。
  • 10000000:迭代次数(b.N)。
  • 128 ns/op:平均每次操作耗时(纳秒)。
  • 48 B/op:每次操作的平均内存分配(字节)。
  • 2 allocs/op:每次操作的平均内存分配次数。

表驱动测试与子测试

  1. 表驱动测试(Table-Driven Tests)
    将多组测试数据放在表格中,减少重复代码:
func TestDivide(t *testing.T) {
    tests := []struct {
        name    string
        a, b    float64
        want    float64
        wantErr bool
    }{
        {"正常除法", 10, 2, 5, false},
        {"除以零", 5, 0, 0, true},
        {"负数", -10, 2, -5, false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := Divide(tt.a, tt.b)
            if (err != nil) != tt.wantErr {
                t.Fatalf("Divide() error = %v, wantErr %v", err, tt.wantErr)
            }
            if got != tt.want {
                t.Errorf("Divide() = %v, want %v", got, tt.want)
            }
        })
    }
}
  1. 子测试(Subtests)
    使用 t.Run() 创建独立的子测试,支持并行执行:
func TestCalculator(t *testing.T) {
    t.Run("加法", func(t *testing.T) {
        if Add(1, 2) != 3 {
            t.Error("加法失败")
        }
    })

    t.Run("减法", func(t *testing.T) {
        if Subtract(5, 3) != 2 {
            t.Error("减法失败")
        }
    })
}

测试覆盖率(Test Coverage)

统计代码被测试覆盖的比例:

go test -coverprofile=coverage.out  # 生成覆盖率报告
go tool cover -html=coverage.out    # 以 HTML 形式查看

模拟(Mocking)与依赖注入

  1. 使用接口实现依赖注入
// 定义接口
type EmailSender interface {
    Send(to, subject, body string) error
}

// 实现类
type UserService struct {
    emailSender EmailSender
}

// 测试时注入模拟实现
func TestUserService_SendWelcomeEmail(t *testing.T) {
    mockSender := &MockEmailSender{}
    service := UserService{emailSender: mockSender}
    
    // 执行测试逻辑...
}
  1. 使用第三方 Mock 库
    如 github.com/golang/mock:
go get github.com/golang/mock/[email protected]

基准测试进阶

  1. 内存分配分析
go test -bench=. -benchmem  # 显示内存分配信息
  1. 并行基准测试
    使用 b.RunParallel() 测试并发性能:
func BenchmarkConcurrentReverse(b *testing.B) {
    s := "Hello, World!"
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            Reverse(s)
        }
    })
}

最佳实践

单元测试

每个测试只验证一个明确的功能点。
使用描述性的测试名称(如 TestAdd_NegativeNumbers)。
避免测试私有函数,优先测试公共接口。

基准测试

关注相对性能变化,而非绝对数值。
使用 -benchtime 调整测试时间:go test -bench=. -benchtime=5s。
避免在基准测试中包含初始化代码。

测试组织

将测试文件与被测试代码放在同一包中。
使用 _test 子目录存放需要特殊权限或复杂设置的测试。

总结

Go 的测试工具链简洁而强大,通过命名约定和标准库提供的 testing 包,开发者可以轻松编写单元测试和基准测试。合理运用表驱动测试、子测试和模拟技术,能够提高测试覆盖率和代码质量,同时通过基准测试持续优化性能。

你可能感兴趣的:(go从入门到精通,golang,单元测试,mock)