go测试文件需以_test.go结尾且与被测代码同目录,包名须与被测包一致(如package utils),不可用package utils_test;测试函数必须为func testxxx(t *testing.t)格式,t.helper()使错误定位到调用处而非内部。

test文件名和包名怎么写才被go test识别
Go 的测试框架只认 *_test.go 结尾的文件,且必须放在被测代码同一目录下(除非是外部测试包)。包声明可以是 package main 或 package xxx,但测试文件**不能**用 package xxx_test 去“隔离”自己——那是为外部测试(如需要访问未导出标识符时)准备的,且必须确保被测包已安装或在 GOPATH/module 路径中可导入。
常见错误:新建 utils_test.go 却写 package utils_test,结果 go test 找不到测试函数,或者报 undefined: MyFunc ——因为当前包看不到 utils 包里的非导出名,也没 import 它。
- 日常单元测试:用
package utils(和被测文件一致),文件名utils_test.go - 想测 unexported 字段或方法:用
package utils_test,并在文件顶部import "your-module/utils",同时确保该包已构建或在本地 module 中 - 别把测试文件放到
test/子目录里——go test默认不递归扫子目录,除非显式指定路径
TestXxx函数签名和t.Helper()有什么实际影响
func TestXxx(t *testing.T) 是唯一被识别为测试的函数签名。名字必须以 Test 开头,后接大写字母开头的名称(如 TestAdd,不能是 testAdd 或 Test_add)。参数必须是 *testing.T,不能是 *testing.B(那是 Benchmark)或自定义类型。
t.Helper() 不影响执行,只影响错误定位:调用它的函数如果调用 t.Error 等,错误行号会指向**调用该函数的地方**,而不是函数内部的 t.Error 行。这对封装断言特别有用。
- 没加
t.Helper():写了个assertEqual(t, got, want),里面报错显示的是assertEqual.go:12,不是你调用它那行 - 加了
t.Helper():同样报错,但显示的是calculator_test.go:33,也就是你写assertEqual(t, a, b)那一行 - 所有辅助测试函数(比如
setupDB(t)、mustOpenFile(t))都应该在第一行加t.Helper()
怎么测 panic、并发逻辑和耗时敏感的场景
测 panic 不能靠 recover 手动捕获再 assert,要用 t.CaptureOutput + func() { ... }() 匿名函数包裹,配合 defer 和 recover,但更推荐 Go 标准做法:func() { ... }() 直接调用并检查是否 panic。
func TestDividePanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("expected panic")
}
}()
Divide(10, 0) // 假设这个函数会 panic
}
并发测试容易漏掉竞态:单纯起 goroutine + time.Sleep 不可靠;要用 t.Parallel()(仅用于互不影响的测试间并发),真正测并发逻辑得靠 channel 同步或 sync.WaitGroup 等待完成。
- 测超时逻辑:用
time.AfterFunc或context.WithTimeout,别用time.Sleep等固定时间 - 测竞态条件:开启 race detector:
go test -race,否则很多 data race 不会暴露 - 避免在测试里写
log.Fatal或os.Exit——它们会直接终止整个go test进程
table-driven 测试怎么组织才不容易崩又易维护
Go 社区普遍用切片字面量 + for range 写 table-driven 测试,核心是让每个 case 有独立变量作用域、能单独失败、失败时输出清晰描述。
关键点不在“用了 struct”,而在**每个 case 是否绑定 name、输入、期望、是否 panic 这些维度**,以及是否用 t.Run 分组。
func TestParseURL(t *testing.T) {
tests := []struct {
name string
input string
wantHost string
wantErr bool
}{
{"empty", "", "", true},
{"valid", "https://example.com/path", "example.com", false},
}
for _, tt := range tests {
tt := tt // 必须复制,否则闭包里 tt 是同一个地址
t.Run(tt.name, func(t *testing.T) {
u, err := ParseURL(tt.input)
if (err != nil) != tt.wantErr {
t.Fatalf("ParseURL(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
}
if !tt.wantErr && u.Host != tt.wantHost {
t.Errorf("ParseURL(%q).Host = %v, want %v", tt.input, u.Host, tt.wantHost)
}
})
}
}
- 每次循环必须
tt := tt,否则所有t.Run闭包共享最后一个tt值 - 用
t.Run而不是注释分隔,这样go test -run=TestParseURL/valid可精确跑单个 case - case 数量超过 5 个后,建议把数据挪到单独
testdata/文件或var testCases = [...]struct{...}常量里,避免测试函数体过长
err != nil,却不比对 err.Error() 或用 errors.Is/errors.As 判断底层错误类型。还有就是忘记清理临时文件、数据库连接、监听端口等资源,导致后续测试失败或端口占用——这些不是语法问题,而是执行环境污染。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











