Go函数测试必须放在_test.go文件中,与被测代码同包;测试函数以Test开头、接收testing.T参数;禁用log.Fatal/os.Exit;推荐表驱动测试和依赖抽象。

用 go test 跑函数测试,必须放在 *_test.go 文件里
Go 的测试框架只识别文件名匹配 *_test.go 的源码,且必须和被测函数在同一个包(除非是外部测试,但函数级单元测试通常不跨包)。如果写在普通 .go 文件里,go test 根本不会加载它。
常见错误:把测试函数写在 main.go 或 utils.go 里,执行 go test 后显示 no tests to run —— 其实不是没写,是文件名不合法。
- 测试文件命名示例:
calculator_test.go,对应calculator.go - 测试函数必须以
Test开头,且接收单个参数*testing.T - 同一包下可有多个
*_test.go,go test会全部扫描
TestXxx(t *testing.T) 里别用 log.Fatal 或 os.Exit
测试函数运行在 testing.T 上下文中,出错时该用 t.Fatal、t.Errorf 或 t.FailNow。直接调 log.Fatal 会终止整个进程,导致后续测试无法执行,而且不会被 go test 正确标记为失败用例。
比如你测一个除法函数,误写成:
立即学习“go语言免费学习笔记(深入)”;
func TestDivide(t *testing.T) {
if _, err := Divide(1, 0); err == nil {
log.Fatal("expected error but got nil") // ❌ 错误:进程退出,测试中断
}
}
正确写法是:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
func TestDivide(t *testing.T) {
_, err := Divide(1, 0)
if err == nil {
t.Fatal("expected error but got nil") // ✅ 仅当前测试失败,不影响其他用例
}
}
-
t.Fatal和t.Error + t.FailNow效果等价,推荐前者 -
t.Error记录错误但继续执行,适合想看“所有断言结果”的场景 - 任何非
t.方法的 panic / exit 都会破坏测试流程
表驱动测试(table-driven)比手写多个 TestXxx 更可靠
给一个函数写 5 种输入,不要硬编码 5 个独立测试函数。用切片定义测试用例,for 循环跑,既避免重复又方便加新 case。Go 标准库和主流项目几乎全用这种方式。
例如测试字符串截取函数 Truncate(s string, n int) string:
func TestTruncate(t *testing.T) {
tests := []struct {
name string
input string
n int
expected string
}{
{"empty", "", 5, ""},
{"short", "hi", 5, "hi"},
{"exact", "hello", 5, "hello"},
{"long", "hello world", 5, "hello"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Truncate(tt.input, tt.n)
if got != tt.expected {
t.Errorf("Truncate(%q, %d) = %q, want %q", tt.input, tt.n, got, tt.expected)
}
})
}
}
-
t.Run支持子测试,失败时能精准定位到name,比如TestTruncate/long - 结构体字段名要清晰,避免用
s、n这类模糊变量名 - 边界值(空、零、负数、超长)必须覆盖,否则容易漏掉 panic 或逻辑错
依赖外部资源的函数,得用接口抽象 + mock,不能真调
如果函数里直接用了 http.Get、os.Open、time.Now() 等,测试会变慢、不稳定、难复现。正确做法是把依赖抽成接口参数传入,测试时传 mock 实现。
比如一个读配置并返回解析结果的函数:
// ❌ 不易测
func LoadConfig(filename string) (map[string]string, error) {
data, err := os.ReadFile(filename)
// ...
}
改成:
// ✅ 可测
type FileReader interface {
ReadFile(filename string) ([]byte, error)
}
func LoadConfig(r FileReader, filename string) (map[string]string, error) {
data, err := r.ReadFile(filename)
// ...
}
// 测试时:
type mockReader struct{}
func (m mockReader) ReadFile(_ string) ([]byte, error) {
return []byte(`key=value`), nil
}
func TestLoadConfig(t *testing.T) {
got, _ := LoadConfig(mockReader{}, "ignored")
// 断言 got
}
- 标准库中
io.Reader、http.RoundTripper等都是这种思路 - 时间相关逻辑用
func() time.Time参数替代硬编码time.Now() - 真在测试里写文件或发 HTTP 请求,等于把单元测试写成了集成测试

















