go测试函数必须命名为testxxx(t *testing.t),文件名以_test.go结尾且与被测代码同目录同包;表驱动测试用t.run和结构体切片组织,错误断言用t.errorf而非assert。

如何给普通函数写测试用例
Go 的测试函数必须放在以 _test.go 结尾的文件里,且函数名必须以 Test 开头、接受单个 *testing.T 参数。不满足任一条件,go test 就不会识别它。
常见错误是把测试函数写成 testMyFunc() 或漏掉 *testing.T 参数,结果运行 go test 时“没报错也没执行”,其实是根本没发现测试函数。
- 测试文件名必须是
xxx_test.go(比如math_utils_test.go) - 函数签名严格为
func TestXxx(t *testing.T),Xxx首字母大写 - 不要在测试函数里用
log.Fatal或os.Exit,会直接终止进程,跳过t.Error后续逻辑 - 想测多个输入组合?用子测试(
t.Run)比写一堆TestXxx1/TestXxx2更清晰
怎样断言返回值是否符合预期
Go 标准库不提供类似 assert.Equal 的断言宏,所有判断都靠 t.Error、t.Errorf、t.Fatal 手动完成。这不是缺陷,而是刻意设计:避免隐藏失败路径,强制你明确处理每个分支。
例如测试一个字符串截取函数 Truncate(s string, n int) string:
func TestTruncate(t *testing.T) {
tests := []struct {
s, want string
n int
}{
{"hello", "he", 2},
{"a", "a", 5},
{"", "", 3},
}
for _, tt := range tests {
got := Truncate(tt.s, tt.n)
if got != tt.want {
t.Errorf("Truncate(%q, %d) = %q, want %q", tt.s, tt.n, got, tt.want)
}
}
}
- 用结构体切片组织多组测试数据,比硬编码
if块更易维护 -
t.Errorf不中断执行,适合批量校验;t.Fatalf遇错即停,适合前置条件检查(如依赖文件是否存在) - 别用
reflect.DeepEqual盲目比较复杂结构——它对nilslice 和空 slice、函数字段、unexported 字段行为不直观,先确认类型再比关键字段
如何模拟外部依赖(HTTP、数据库等)
真实调用外部服务会让测试变慢、不稳定、难复现。Go 推荐用接口抽象依赖,再传入 mock 实现。不是用第三方 mock 框架,而是靠语言特性做轻量替换。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
比如一个依赖 HTTP 客户端的函数:
type HTTPDoer interface {
Do(*http.Request) (*http.Response, error)
}
func FetchUser(client HTTPDoer, id int) (string, error) {
req, _ := http.NewRequest("GET", fmt.Sprintf("https://api.example.com/users/%d", id), nil)
resp, err := client.Do(req)
// ...
}
测试时传入自定义结构体,而非真实 http.Client:
type mockDoer struct {
resp *http.Response
err error
}
func (m mockDoer) Do(*http.Request) (*http.Response, error) {
return m.resp, m.err
}
func TestFetchUser(t *testing.T) {
client := mockDoer{
resp: &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(`{"name":"alice"}`))},
err: nil,
}
name, err := FetchUser(client, 123)
if err != nil || name != "alice" {
t.Errorf("unexpected result: %v, %v", name, err)
}
}
- 接口定义越小越好(如只含
Do方法),便于 mock 和后期替换 - 不要在生产代码里直接 new
http.Client,改为接收接口参数或通过构造函数注入 - 时间相关逻辑(如超时、重试)用
time.Now或time.Sleep的包装接口,方便在测试中快进或冻结时间
为什么 go test -race 要常开
竞态检测器(race detector)不是可选优化项,而是并发测试的底线工具。Go 的 go 关键字和 channel 让并发写起来简单,但数据竞争往往只在高负载或特定调度下暴露,本地跑一百次都可能不复现。
- 只要代码含 goroutine、共享变量、非同步读写,就必须用
go test -race运行一次 - 竞态报告里的
Previous write at/Current read at行号非常精准,直接定位到问题语句 - 别依赖 “我没看到 panic” 来判断线程安全——race detector 是唯一可靠手段,且开销可控(约 2–5 倍慢,内存多 5–10 倍)
- CI 流水线里应固定加
-race,避免合并后才爆雷
测试不是为了凑覆盖率数字,而是让每次 go test 都能戳中那些「平时不闹、上线就崩」的角落。尤其是接口边界、错误路径、并发共享点——这些地方不写测试,等于留了活口。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










