2. Go testing 包入门:testing.T 的基本用法、Test 函数签名与表格驱动测试

在 Go 语言生态中,标准库 testing 包是单元测试、基准测试与示例测试的基石。对于 Kafka 压测场景,扎实的 testing 基础能让你快速构建可重复、可验证的测试闭环。本节将聚焦 testing.T 的核心用法、Test 函数签名规范以及 Go 社区最推崇的表格驱动测试(Table Driven Test)模式。

2.1 testing.T 的基本用法

testing.T 是测试函数的控制句柄,它提供了日志输出、失败标记、跳过测试、并行控制等关键方法。下表列出了最常用的方法及其语义:

方法 签名 说明
Error func (c *T) Error(args ...any) 记录失败日志,标记测试失败,但继续执行当前测试函数
Errorf func (c *T) Errorf(format string, args ...any) 格式化版 Error,支持 %v、%d 等占位符
Fatal func (c *T) Fatal(args ...any) 记录失败日志,标记测试失败,立即终止当前 goroutine
Fatalf func (c *T) Fatalf(format string, args ...any) 格式化版 Fatal
Log func (c *T) Log(args ...any) 记录普通日志,仅在测试失败或 -v 模式下输出
Logf func (c *T) Logf(format string, args ...any) 格式化版 Log
Skip func (c *T) Skip(args ...any) 跳过当前测试,标记为 SKIP 状态
Parallel func (c *T) Parallel() 标记测试为可并行执行,与 -parallel 标志配合
Run func (c *T) Run(name string, f func(t *T)) bool 运行子测试,返回子测试是否通过
压测场景提示: 在 Kafka 生产者/消费者测试中,Fatal 常用于连接失败等不可恢复错误;Error 用于消息内容校验失败,允许继续测试后续消息;Logf 配合 -v 标志可输出每条消息的 offset 与 partition 信息,便于调试。

2.2 Test 函数签名规范

Go 编译器通过函数签名自动识别测试函数,规则严格且简洁:

  • 文件名:必须以 _test.go 结尾,例如 producer_test.go。
  • 函数名:以 Test 开头,后接首字母大写的标识符,例如 TestSendMessage。
  • 参数:唯一参数为 *testing.T,无返回值。
  • 包归属:测试文件可以与被测文件同包(白盒测试),也可以放在 package xxx_test(黑盒测试)。
// 白盒测试:与被测代码同包
package kafka

import "testing"

func TestProduceMessage(t *testing.T) {
    // 可以访问包内未导出标识符
    producer := NewProducer("localhost:9092")
    err := producer.Send("test-topic", []byte("hello"))
    if err != nil {
        t.Fatalf("Send failed: %v", err)
    }
    t.Log("Message sent successfully")
}

// 黑盒测试:使用外部包视角
package kafka_test

import (
    "testing"
    "yourmodule/kafka"
)

func TestConsumerGroup(t *testing.T) {
    consumer, err := kafka.NewConsumerGroup("group-1", []string{"localhost:9092"})
    if err != nil {
        t.Fatalf("NewConsumerGroup failed: %v", err)
    }
    defer consumer.Close()
    t.Log("Consumer group created")
}
常见陷阱: 测试函数名如果写成 testProduceMessage(小写 t)或 TestproduceMessage(P 小写),Go 编译器会静默忽略,不会执行任何测试逻辑。运行 go test -run . 时务必确认测试函数被正确匹配。

2.3 表格驱动测试(Table Driven Test)模式

表格驱动测试是 Go 社区的标准实践,它将测试输入与期望输出组织为结构体切片,通过循环驱动测试逻辑。这种模式消除了重复代码,使新增测试用例只需追加一行数据。

2.3.1 核心结构

// 定义测试用例结构体
type produceTestCase struct {
    name      string // 子测试名称,用于标识
    topic     string
    key       string
    value     string
    wantErr   bool
    wantPartition int32
}

func TestProduceToPartition(t *testing.T) {
    // 表格:所有测试用例
    tests := []produceTestCase{
        {
            name:          "normal message to partition 0",
            topic:         "test-topic",
            key:           "key-1",
            value:         "value-1",
            wantErr:       false,
            wantPartition: 0,
        },
        {
            name:          "empty topic should fail",
            topic:         "",
            key:           "key-2",
            value:         "value-2",
            wantErr:       true,
            wantPartition: -1,
        },
        {
            name:          "nil value should be allowed",
            topic:         "test-topic",
            key:           "key-3",
            value:         "",
            wantErr:       false,
            wantPartition: 1,
        },
    }

    // 循环驱动
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            producer := NewProducer("localhost:9092")
            partition, err := producer.SendWithPartition(tt.topic, []byte(tt.key), []byte(tt.value))

            if (err != nil) != tt.wantErr {
                t.Errorf("SendWithPartition() error = %v, wantErr %v", err, tt.wantErr)
            }
            if !tt.wantErr && partition != tt.wantPartition {
                t.Errorf("SendWithPartition() partition = %d, want %d", partition, tt.wantPartition)
            }
        })
    }
}

2.3.2 表格驱动测试的视觉流程

下图展示了表格驱动测试从定义到执行的完整数据流:

表格驱动测试执行流程 测试用例表格 []produceTestCase name / topic / key / value wantErr / wantPartition for range 循环 t.Run(tt.name, ...) 每次迭代一个用例 独立子测试上下文 被测函数 SendWithPartition (topic, key, value) → (partition, err) 断言校验 if (err != nil) != tt.wantErr → t.Errorf if partition != tt.wantPartition → t.Errorf 测试结果汇总 PASS / FAIL(每个子测试独立报告) 新增用例只需在表格中 追加一行结构体数据

2.3.3 表格驱动测试的优势

维度 传统逐函数测试 表格驱动测试
代码重复 每个用例重复 setup、调用、断言代码 一次循环,数据驱动,零重复
可读性 逻辑与数据混在一起,难以快速定位边界 数据与逻辑分离,表格一目了然
扩展性 新增用例需复制整个函数或新增函数 仅追加一行结构体数据
子测试支持 通常无子测试,失败时难以定位具体输入 t.Run 自动生成子测试名称,go test -run TestXxx/用例名 可单独执行
CI 报告 失败信息模糊,需手动加日志 子测试名称 + 格式化 Errorf 输出精确输入/期望/实际

2.3.4 在 Kafka 测试中的实战模式

对于 Kafka 生产者/消费者测试,表格驱动模式可以覆盖多种消息场景:

type consumeTestCase struct {
    name       string
    topic      string
    partition  int32
    offset     int64
    wantMsg    string
    wantErr    bool
}

func TestConsumeMessage(t *testing.T) {
    tests := []consumeTestCase{
        {name: "consume from offset 0", topic: "orders", partition: 0, offset: 0, wantMsg: "order-1", wantErr: false},
        {name: "consume from non-existent partition", topic: "orders", partition: 99, offset: 0, wantMsg: "", wantErr: true},
        {name: "consume from negative offset", topic: "orders", partition: 0, offset: -1, wantMsg: "", wantErr: true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            consumer := NewConsumer("localhost:9092")
            msg, err := consumer.Consume(tt.topic, tt.partition, tt.offset)
            if (err != nil) != tt.wantErr {
                t.Errorf("Consume() error = %v, wantErr %v", err, tt.wantErr)
            }
            if !tt.wantErr && string(msg.Value) != tt.wantMsg {
                t.Errorf("Consume() msg = %s, want %s", string(msg.Value), tt.wantMsg)
            }
        })
    }
}
压测闭环关键点: 表格驱动测试天然适合压测场景的“参数矩阵”需求。你可以将 Kafka 集群地址、topic 分区数、消息大小、acks 配置等作为表格字段,快速构建不同压力配置下的功能验证用例,确保压测前功能正确性已通过表格覆盖。

2.4 本节小结

  • testing.T 提供 Error/Fatal/Log/Skip/Parallel/Run 等核心方法,是测试执行的控制中心。
  • Test 函数签名 必须严格遵循 func TestXxx(t *testing.T) 格式,文件名以 _test.go 结尾。
  • 表格驱动测试 通过结构体切片 + t.Run 子测试实现数据与逻辑分离,是 Go 社区的标准测试模式。
  • 在 Kafka 压测闭环中,表格驱动测试为后续的基准测试(testing.B)和压力测试提供了可复用的用例组织范式。