go单元测试推荐表驱动模式,以testing.t编写基础测试,用结构体切片定义用例,t.run命名子测试,覆盖边界与错误场景,并通过t.cleanup管理资源。

Go 的单元测试强调简洁、可读和可维护,testing 包原生支持、无需第三方依赖,而表驱动测试(Table-Driven Tests)是 Go 社区广泛采用的推荐模式——它把多个测试用例组织成结构化数据,统一执行逻辑,显著减少重复代码,提升覆盖与可维护性。
用 testing.T 编写基础测试函数
每个测试函数必须以 Test 开头,接收 *testing.T 参数。调用 t.Error、t.Errorf 表示失败;t.Fatal/t.Fatalf 会立即终止当前测试函数。注意:不建议用 panic 或 log 输出替代 t.Error,否则测试框架无法正确统计失败数或生成报告。
- 测试文件名必须为
xxx_test.go,且包声明通常与被测代码同包(如被测在utils/,测试也声明package utils) - 运行测试用
go test,加-v查看详细输出,-run=TestName运行单个测试 - 避免在测试中依赖外部状态(如文件、网络、全局变量),必要时用
t.Cleanup恢复环境
表驱动测试:用结构体切片定义测试用例
核心思想是将输入、预期输出、描述等封装进一个结构体,再用 for 循环遍历执行。这样新增用例只需追加结构体元素,无需复制粘贴测试逻辑。
例如测试一个字符串截断函数:
立即学习“go语言免费学习笔记(深入)”;
func TestTruncate(t *testing.T) {
tests := []struct {
name string
input string
maxLen int
expected string
}{
{"empty", "", 5, ""},
{"short", "hi", 5, "hi"},
{"long", "hello world", 5, "hello..."},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Truncate(tt.input, tt.maxLen)
if got != tt.expected {
t.Errorf("Truncate(%q, %d) = %q, want %q", tt.input, tt.maxLen, got, tt.expected)
}
})
}
}- 使用
t.Run为每个子测试命名,便于定位失败项,也支持并发运行(加t.Parallel()) - 结构体字段按需设计,常见包括
name、input、expected、errExpected(是否期望错误) - 避免在循环内直接使用循环变量(如
tt)启动 goroutine,应传参捕获值
处理错误与边界场景的惯用写法
真实业务逻辑常涉及错误路径,表驱动测试同样适用。关键是在结构体中增加错误相关字段,并在断言时区分成功与失败分支。
- 若函数返回
(result, error),结构体可加errExpected bool或expectedErrType reflect.Type - 用
errors.Is或errors.As判断错误类型,比直接比较错误消息更健壮 - 对 panic 场景,可用
defer/recover捕获并验证,但应明确注释“该测试验证 panic 行为” - 边界值(如空切片、负数、nil 指针)务必纳入表格,尤其影响逻辑分支的输入
辅助技巧:测试工具函数与 setup/teardown
当多个测试需要共用初始化或清理逻辑时,可提取为私有工具函数,而非使用全局 setup。Go 不提供类似 JUnit 的生命周期钩子,但可通过组合实现清晰控制。
- setup 函数返回需要清理的资源(如临时目录、mock server),配合
t.Cleanup注册清理动作 - 避免在测试函数外初始化共享状态(如全局 map),会导致测试间干扰;每个测试应尽量独立
- 对耗时操作(如 I/O),用
t.SkipNow()或if testing.Short() { t.Skip(...) }支持短测试模式 - 用
testify/assert等库可简化断言(非必需),但标准库已足够应对绝大多数场景











