Go测试物理隔离靠命名约定和testing.Short():单元测试用xxx_test.go,集成测试用xxx_integration_test.go并检查t.Short()跳过;依赖通过接口注入或构造函数参数化实现mock/real切换,禁用全局单例;-race默认禁用集成测试以避免数据竞争。

Go 单元测试和集成测试怎么物理隔离
Go 官方测试框架本身不区分“单元”或“集成”,全靠你用 go test 的参数和文件命名约定来划界。不隔离,go test ./... 一跑全上,数据库连不上、网络超时、环境变量缺失,测试直接挂——不是代码错,是测试没分层。
实操就两条铁律:
- 单元测试文件必须叫
xxx_test.go,且函数名以Test开头;集成测试文件也得叫xxx_test.go,但加_integration后缀(如user_service_integration_test.go) - 所有集成测试函数必须显式检查
testing.Short(),并在开头加if testing.Short() { t.Skip("skipping integration test") }
这样你就能用 go test -short ./... 快速跑完单元测试,CI 默认开 -short,而手动验证时跑 go test ./... -run Integration 即可。
如何让集成测试跳过 CI 环境
CI 环境通常没数据库、没 Redis、没真实 HTTP 服务,硬跑集成测试=必败。别指望“自动检测环境”,Go 没这种魔法,得你自己控。
立即学习“go语言免费学习笔记(深入)”;
关键不是判断“是不是 CI”,而是判断“依赖是否就绪”。比如:
- 数据库集成测试前,先尝试
sql.Open+db.Ping(),失败就t.Skipf("no DB available: %v", err) - HTTP 集成测试前,用
http.Get("http://localhost:8080/health")探活,超时就跳过 - 永远别用
os.Getenv("CI") == "true"做开关——本地开发也可能设这个变量,结果误跳
更稳的做法是:集成测试只在明确传入 -test.run=Integration 且依赖可用时才执行,否则一律跳过。
mock 和 real 之间怎么切得干净
分层测试最常崩在“以为 mock 了,其实调了真服务”。比如你 mock 了 *sql.DB,但忘了 mock sql.Open,它还是连上了本地 PostgreSQL。
真正可控的切法只有两种:
- 接口注入:把依赖抽象成接口(如
type DBExecutor interface { Query(...)),测试时传mockDB,生产传realDB;别直接 newsql.DB - 构造函数参数化:把数据库连接、HTTP client 等作为参数传给 service 构造函数,测试时塞 mock,main 函数里才 new 真的
别碰全局变量、单例、init 函数初始化的 client——它们会让测试互相污染,哪怕加 t.Cleanup 也救不回来。
go test -race 跑集成测试会炸吗
会,而且很常见。集成测试常启 goroutine、共享内存、改全局状态,-race 一开就报 data race,但问题往往不在业务代码,而在测试写法本身。
典型雷区:
- 多个集成测试共用同一个
http.Server,没等前一个关完,后一个就 bind 同个端口,panic 或 race - 测试里用
time.Sleep(100 * time.Millisecond)等异步完成,race detector 捕捉不到时序,但实际执行时可能漏断言 - 并发跑集成测试(
go test -p=4)时,多个测试同时操作同一张 SQLite 文件或临时目录
对策很简单:集成测试默认禁用 -race,只在单元测试阶段开;如果非要测并发行为,用 t.Parallel() + 独立资源(如随机端口、临时 sqlite 文件路径),别省那几行代码。
分层不是为了多写几个文件,是让每次 go test 都有确定性——单元测试秒出结果,集成测试可选、可重试、不污染。否则所谓架构,只是把混乱从代码里搬到了测试目录下。










