表驱动测试是Go中用结构体切片定义测试用例、通过t.Run逐个执行的惯用法;需明确name字段、避免tt闭包陷阱、正确校验error,不适用于逻辑差异大或I/O密集场景。
Go 语言没有内置的参数化测试机制,但用切片 + for

核心是定义测试用例结构体、填充 tests 切片、遍历调用被测函数并比对结果。注意:每个测试用例必须有唯一可读的 name 字段,便于定位失败项。
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive", 2, 3, 5},
{"negative", -1, -2, -3},
{"zero", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.expected {
t.Errorf("Add(%d,%d) = %d, want %d", tt.a, tt.b, got, tt.expected)
}
})
}
}
t.Run() 是关键:它为每个子测试创建独立作用域,支持并发运行、单独重试、清晰的失败路径input/output 这类泛称,比如用 username、expectedErr 更易读tt 变量做闭包(常见坑!),必须传入 t.Run 的匿名函数内或显式复制,否则所有子测试共享最后一个 tt 值真实函数常返回 (result, error),表驱动测试需同时校验值与错误。建议用指针比较错误(errors.Is 或 errors.As),而非字符串匹配。
func TestParseInt(t *testing.T) {
tests := []struct {
name string
input string
expected int
expectedErr error
}{
{"valid", "42", 42, nil},
{"empty", "", 0, strconv.ErrSyntax},
{"space", " 123 ", 123, nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := strconv.Atoi(tt.input)
if !errors.Is(err, tt.expectedErr) {
t.Errorf("Atoi(%q) error = %v, want %v", tt.input, err, tt.expectedErr)
return
}
if got != tt.expected {
t.Errorf("Atoi(%q) = %d, want %d", tt.input, got, tt.expected)
}
})
}
}
expectedErr 是具体错误类型(如 io.EOF),用 errors.Is(err, io.EOF)
Code),用 errors.As(err, &target)
err == nil 和 expectedErr == nil 的双重判断逻辑,漏判会导致 panic 或误通过不是所有场景都适合。当测试逻辑差异大、setup/teardown 成本高、或需要精细控制执行顺序时,硬塞进一张表反而增加维护负担。
log.Printf 或断点),混在循环里会干扰其他 case真正容易被忽略的是:表驱动测试的可读性完全依赖于结构体字段命名和 name 描述质量。一个叫 test1 的 case 失败时,你得翻源码才能知道它到底在测什么。