go test会静默跳过Test函数而非报错:函数名必须为TestXxx(首字母大写+驼峰)、参数严格为*testing.T,文件名须为xxx_test.go且与被测代码同包同目录。

Test函数签名写错,go test会静默跳过
不是报错,而是根本不会执行——这是新手最常踩的坑。只要Test函数参数不是*testing.T,或者函数名不符合TestXxx(首字母大写、后缀也必须大写开头),go test就当它不存在。
-
func TestAdd(t testing.T):少指针,编译失败 -
func testAdd(t *testing.T):小写开头,被忽略 -
func Test_add(t *testing.T):下划线分隔,不识别 -
func TESTADD(t *testing.T):全大写,跳过
验证方式很简单:go test -v会列出所有运行的测试名;如果没看到你的函数,八成是命名或签名问题。
_test.go 文件放错位置,包内私有符号不可见
测试文件必须和被测代码在同一个包里,否则无法调用未导出函数或访问包级变量。常见错误是把user_test.go放到test/子目录,或误建为user_test.go但包声明写成package test。
- 正确结构:
user.go和user_test.go同目录,都声明package user - 错误结构:
user_test.go在test/user_test.go,包名变成package test→ 无法访问user包里的newService()等非导出函数 - 文件名必须是
_test.go结尾:user_tests.go或test_user.go都不会被识别
不抽接口、不传依赖,就不是单元测试
直接在UserService.Login()里调db.Query,然后测试时连真实数据库——这不是单元测试,是集成测试,而且极不稳定。Go 单测有效的前提是可隔离、可控制。
立即学习“go语言免费学习笔记(深入)”;
- 把外部依赖抽象成接口:
type DB interface { Query(query string, args ...any) (*Rows, error) } - 业务逻辑接收该接口:
func (s *UserService) Login(db DB, username string) error - 测试时传入 fake 实现:
fakeDB := &mockDB{rows: nil, err: errors.New("timeout")} - 避免 fake 里加逻辑:不要根据 ID 返回不同用户,静态返回更可靠;动态行为会让测试随实现漂移
表驱动测试里用 t.Run,但别掉进闭包陷阱
写for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ... }) }很常见,但如果在闭包里直接引用tt字段,所有子测试会共享最后一次迭代的值。
- 错误写法:
t.Run(tt.name, func(t *testing.T) { result := Add(tt.a, tt.b) })→tt被所有 goroutine 共享 - 正确写法:在循环体内加一层局部变量,如
tc := tt; t.Run(tc.name, func(t *testing.T) { result := Add(tc.a, tc.b) }) - 子测试名建议带语义:
"Add with negative numbers"比"case 2"更容易定位失败原因
真正难的不是写测试,是让每个if err != nil分支、每个defer清理路径、每个context.Err()退出点都被触发——这些地方最容易藏 bug,也最难覆盖到。


















