测试文件必须声明与源码相同的包名(如 package utils),而非独立包;Test_开头函数可测私有函数;应通过重构解耦依赖而非硬导出或反射。

测试文件必须和源码同包,不是同目录就行
Go 的私有函数(首字母小写)只在定义它的包内可见。很多人把 utils_test.go 放在 utils/ 目录下,却写 package utils_test,结果一跑就报 undefined: validateToken——这不是路径错了,是包隔离了。
- ✅ 正确做法:测试文件声明为
package utils(和源码一致),后缀仍是_test.go - ❌ 错误做法:用
package utils_test,哪怕文件在同一个目录,也进不了同一个包作用域 - ⚠️ 注意:
go test会自动识别_test.go文件,但不会自动修正包名;IDE 有时默认帮你生成xxx_test包,得手动改
Test_ 命名是合法且稳定的,不是 hack
go test 明确支持以 Test_ 开头的函数(Go 1.21+ 官方确认),用于测试包内未导出函数。它不是临时技巧,而是社区广泛采用、工具链稳定支持的命名约定。
- ✅ 可用:
func Test_validateToken(t *testing.T)—— 能被go test发现并执行 - ❌ 无效:
func test_validateToken(t *testing.T)(小写开头)或func Testvalidatetoken(t *testing.T)(驼峰不规范) - ⚠️ 不要混用:
TestValidateToken是给导出函数用的;Test_validateToken才对应私有函数validateToken
别为了测试硬导出,重构比绕过规则更可持续
看到 func parseConfig() 太复杂,有人直接改成 ParseConfig() 再测——这等于把实现细节钉死在 API 上。下次你想重命名、加 context.Context、拆参数,所有调用方都得跟着改,包括测试。
- ✅ 推荐做法:把纯逻辑抽成新导出函数,比如
ParseConfigBytes([]byte) (Config, error),再单独写TestParseConfigBytes - ✅ 保留原私有函数做胶水层:负责读文件、处理错误、注入依赖,它本身只需轻量覆盖
- ⚠️ 避免反射:
reflect.ValueOf(validateToken).Call()能跑,但让测试变成“运行时契约”:签名一变就 panic,IDE 跳不到定义,静态检查失效
真正难测的往往不是私有性,而是隐式依赖
私有函数测不出来,大概率不是因为访问限制,而是它偷偷依赖了全局状态、time.Now()、os.Getenv() 或未 mock 的 HTTP 客户端。
- ✅ 先解耦:把时间、配置、外部调用作为参数传入,比如
func validateToken(token string, now func() time.Time, cfg Config) error - ✅ 测试时传入可控值:
now: func() time.Time { return time.Unix(123, 0) } - ⚠️ 如果一个私有函数从没被任何导出函数调用,或者改了也不影响外部行为,那它大概率不该存在——删掉比测它更安全

















