go单元测试由语言原生支持,需严格遵循_test.go命名、testxxx函数签名;应隔离外部依赖、使用接口抽象和mock;推荐表格驱动+子测试+t.run组织,并注意并发安全与清理。

Go 的单元测试不是靠框架驱动,而是语言原生支持——go test 命令和 *testing.T 类型就是全部基础。写法简单,但容易忽略边界、依赖隔离和测试可维护性。
测试文件命名和函数签名必须严格匹配
Go 要求测试文件名以 _test.go 结尾,且测试函数必须是 func TestXxx(*testing.T) 形式,其中 Xxx 首字母大写。不满足任一条件,go test 就会直接跳过。
-
calculator_test.go✅;calculator_tests.go❌(不会被识别) -
func TestAdd(t *testing.T)✅;func testAdd(t *testing.T)❌(小写开头不运行) -
func TestAdd(t *testing.B)❌(类型错,这是基准测试用的)
避免在测试中直接调用真实外部依赖
比如数据库、HTTP 客户端、时间相关操作。硬编码调用会让测试变慢、不稳定、难以复现。
- 用接口抽象依赖:把
*sql.DB换成DBExecutor接口,测试时传入 mock 实现 - 时间敏感逻辑用
time.Now的封装,例如clock.Now(),测试时注入固定时间的FixedClock - HTTP 请求不要直连
http.DefaultClient,改用可替换的http.Client字段或参数
使用 t.Run 组织子测试并提前失败
单个测试函数里验证多个输入场景时,用 t.Run 不仅能分组输出、独立标记失败,还能配合 t.Fatal 实现“某个子测试挂了不影响其他子测试运行”。
func TestParseURL(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
}{
{"empty", "", true},
{"valid", "https://example.com", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := url.Parse(tt.input)
if (err != nil) != tt.wantErr {
t.Fatalf("Parse() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
表格驱动测试中别漏掉 t.Parallel() 和清理逻辑
并发执行子测试能显著缩短整体耗时,但前提是测试间无共享状态;同时,如果测试创建了临时文件、启动了 goroutine 或修改了全局变量,必须显式清理。
- 每个
t.Run内尽早加t.Parallel()(放在最开头) - 用
defer os.Remove清理临时文件,或用os.MkdirTemp("", "")自动生成唯一路径 - 启动了 goroutine 的测试,要加超时控制和
done := make(chan struct{})配合select等待结束 - 修改了
os.Args或环境变量的测试,务必在结尾用defer恢复原始值
真正难的不是写第一个 TestXxx,而是让后续每次重构都敢删代码——这取决于你是否在测试里藏了隐式依赖、是否把错误路径当装饰、是否把“跑通了”当成“覆盖全了”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











