Go单元测试需满足文件名以_test.go结尾、函数名以Test开头、参数为*testing.T;go test默认只运行当前目录测试,-run支持正则匹配;应避免log.Fatal/os.Exit,改用t.Fatal/t.Error;推荐表驱动测试与接口抽象解耦外部依赖。

如何用 go test 运行最简单元测试
Go 的单元测试不需要额外框架,只要文件名以 _test.go 结尾、函数名以 Test 开头、参数为 *testing.T,就能被 go test 自动识别。不满足任一条件,测试就不会执行。
-
go test默认只运行当前目录下的*_test.go文件,不会递归子目录 - 测试函数必须是导出的(首字母大写),但名字本身不用导出 ——
TestAdd合法,testAdd会被忽略 - 若想只跑某个测试,用
go test -run=TestAdd,-run后跟的是正则匹配名,不是函数全名
testing.T 的常见误用:别在测试里用 log.Fatal 或 os.Exit
测试中调用 log.Fatal、panic 或 os.Exit(1) 会导致整个测试进程退出,后续测试全部中断,且 go test 会报 exit status 1,掩盖真实失败原因。
- 正确做法是调用
t.Fatal或t.Errorf+t.FailNow(),它们只终止当前测试函数,不影响其他测试 -
t.Error记录错误但继续执行,适合检查多个断言;t.Fatal遇错即停,适合前置条件校验(如初始化失败) - 如果被测函数内部调用了
os.Exit(比如 CLI 工具),需通过接口抽象或构建可替换的os.Exit函数变量来解耦,否则无法测试
表驱动测试怎么写才不重复又易维护
Go 社区推荐用结构体切片定义测试用例,避免大量复制粘贴的 TestXxx 函数。关键在于把输入、期望输出、描述聚合成一个数据项,再用循环统一执行断言。
func TestParseDuration(t *testing.T) {
cases := []struct {
name string
input string
expected time.Duration
wantErr bool
}{
{"zero", "0s", 0, false},
{"seconds", "30s", 30 * time.Second, false},
{"invalid", "1y", 0, true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := time.ParseDuration(tc.input)
if tc.wantErr {
if err == nil {
t.Fatal("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tc.expected {
t.Errorf("ParseDuration(%q) = %v, want %v", tc.input, got, tc.expected)
}
})
}
}-
t.Run创建子测试,让每个用例独立计时、独立失败,输出带名字(如--- FAIL: TestParseDuration/seconds) - 结构体字段名要语义清晰,
name必须有,否则子测试名是空字符串,调试困难 - 不要在循环里直接用
tc变量做闭包 —— Go 中循环变量复用,所有 goroutine 或延迟函数会看到最后一个值;必须用tc := tc显式拷贝
测试依赖外部服务?用接口+模拟(mock)而不是真实调用
测试不应该依赖数据库、HTTP 服务或文件系统。真实 I/O 不仅慢、不稳定,还会让测试变成集成测试。Go 推崇“依赖倒置”:把具体实现抽成接口,测试时传入内存实现。
立即学习“go语言免费学习笔记(深入)”;
- 例如 HTTP 客户端,不要直接用
http.DefaultClient,而是定义type HTTPDoer interface { Do(*http.Request) (*http.Response, error) } - 测试时传入自定义类型,
Do方法返回预设响应,完全绕过网络 - 对标准库类型(如
*sql.DB)也一样:定义Querier接口,只暴露QueryRow等方法,测试时用内存 map 模拟结果 - 避免使用第三方 mock 库(如
gomock),多数场景纯 Go 就够用;mock 库增加编译依赖和学习成本,且容易过度模拟
测试最难的不是写断言,而是识别哪些逻辑该被隔离、哪些状态需要重置。比如全局变量、单例、时间相关代码(time.Now())、随机数生成器(rand.Intn)——这些都得通过参数注入或接口抽象才能可控。没做这一步,测试就只是“能跑”,不是“可信”。










