测试必须隔离状态、避免共享可变资源,每个测试独占实例;TestMain仅限只读初始化;testdata路径需动态构建且文件语义化;辅助函数须调t.Helper()并返回error。

测试数据必须隔离,不能共享可变状态
Go测试默认并发执行(go test 默认启用 -p 并行),一旦多个测试共用一个全局变量、数据库连接或内存缓存,就极易出现“前一个测试改了数据,后一个测试读到脏值”的问题。这不是偶发,是必然污染。
- 避免在
init()或包级变量中初始化可变资源(如var db *sql.DB) - 每个测试应独占自己的数据库实例(如用
sqlite.Memory)、独立的 mock 服务端口、或通过tempfile创建临时目录 - 若必须复用资源(如 HTTP server),确保其 handler 是无状态的,且每次请求都重置内部状态(比如用
sync.Map按t.Name()隔离数据)
TestMain 中只做一次性只读初始化
TestMain 很容易被当成“万能初始化入口”,但滥用它会让测试失去可重复性——比如在里面启动一个全局 HTTP server,却没保证每次测试前清空它的路由表或中间件栈,后续测试就可能因路由冲突或中间件副作用而失败。
- 适合放:加载配置文件(
json.Unmarshal到只读结构体)、构建不可变的 fixture 数据(如ValidUser常量)、预编译正则表达式 - 禁止放:打开可写数据库连接、注册全局 hook、修改
os.Environ()、启动监听端口(除非你明确控制端口分配并确保每次 clean) - 务必在
m.Run()后执行teardown,哪怕只是os.RemoveAll(tempDir),否则下次运行可能因残留文件失败
testdata 目录要配合 filepath.Join 动态读取
硬编码路径(如 "./testdata/user.json")会导致测试在不同工作目录下失败;而直接把 JSON 内容写进测试函数里,又让数据无法复用、难维护、易过期。
- 始终用
filepath.Join("testdata", "user_valid.json")构建路径,配合testutil.MustReadFile(t, path)封装读取逻辑 - 文件名需带语义:不叫
1.json,而叫user_with_role_admin.json,方便快速定位用途 - 所有
testdata/下的文件必须提交进 Git,且禁止存放密钥、token 等敏感内容(CI 环境可能直接 dump)
辅助函数必须调 t.Helper(),且不自行 t.Fatal
不加 t.Helper() 的辅助函数,一旦出错,报错行会指向函数内部而非真实调用处,导致你反复检查“明明输入是对的,怎么断言失败了?”——其实是因为错误堆栈藏在 helper 里。
立即学习“go语言免费学习笔记(深入)”;
- 每个辅助函数第一行必须是
t.Helper() - 不要在辅助函数里调
t.Fatal或t.Error,而是返回error,由测试函数自己判断是否中断(例如:setupDB 返回 error,测试里用if err != nil { t.Fatal(err) }) - 链式构造器(如
NewUser().Role("admin").Build())也要在每个方法里加t.Helper(),否则链式调用出错时定位不到源头










