Go集成测试需用-build标签隔离,禁止单元测试误连依赖;HTTP测试依目标选httptest.NewServer或真实server;依赖服务应通过testcontainers-go或内存库管理;入口逻辑须重构为可测函数以覆盖启动链路。

集成测试不是 go test 默认就跑的
Go 的集成测试必须显式隔离,否则本地 go test 会误连数据库、卡在端口冲突、拖慢 CI。它和单元测试是两类事——单元测试验证逻辑分支,集成测试验证“服务真能起来、接口真能通、依赖真能连”。不加控制地混在一起,等于把开发机当测试环境用。
- 必须用构建标签区分:
//go:build integration(Go 1.17+)或// +build integration(旧版) - 运行时强制指定:
go test -tags=integration ./...,不加-tags就完全不会执行 - 别在
main_test.go或共用 setup 函数里启动 DB/HTTP 服务,否则普通单元测试也会触发 - CI 流水线里要明确写两遍:
go test ./...(单元) +go test -tags=integration ./...(集成)
HTTP 集成测试该用 httptest.NewServer 还是真实 http.Server?
答案取决于你到底想测什么。httptest.NewServer 只包装 handler,不走 main、不初始化中间件、不加载配置、不处理信号——它适合快速验证路由返回值,但测不出 graceful shutdown 是否生效、日志是否注入、超时配置是否被读取。
- 测 handler 行为(比如 JSON 序列化、状态码)→ 用
httptest.NewServer,快且干净 - 测完整服务生命周期(启动、健康检查、配置加载、关闭)→ 必须导出
NewServer()或Run()函数,在测试中起真实http.Server - 真实 server 启动后要配随机端口(如
":0"),避免 CI 并发时冲突;关闭必须用srv.Shutdown(),不能只杀 goroutine - 若用
exec.Command("go", "run", "main.go")启进程,记得设cmd.Env注入测试专用配置(如DATABASE_URL=sqlite://:memory:)
testcontainers-go 不是银弹,但不用它更麻烦
本地跑 PostgreSQL/Redis/Kafka 时,硬写 localhost:5432 会导致测试在别人机器上失败、CI 环境缺失依赖、每次跑完数据残留。用 testcontainers-go 起临时容器是最稳的方案,但它有前提:Docker daemon 必须可用,且容器启动可能失败。
- 启动后务必加健康检查,比如对 Postgres 执行
SELECT 1,对 HTTP 服务发HEAD请求 - 失败时加简单重试(最多 3 次,
time.Sleep(100 * time.Millisecond)),别用time.AfterFunc防 goroutine 泄漏 - 清理必须放
defer里,且检查返回值:if err := container.Terminate(ctx); err != nil { t.Log("terminate failed:", err) } - 若 CI 不支持 Docker(如某些 macOS runner),退而求其次:用内存数据库(
sqlite://:memory:)、mock HTTP 客户端、或跳过该类测试
覆盖率报告里看不到中间件和 main,说明没测到点子上
go test -cover 显示覆盖率低,往往不是代码写得差,而是测试根本没跑进生产启动路径。用 httptest.NewServer 包 handler,只能覆盖到 func(w, r) 里面那几行;但中间件注册顺序、DB 连接池初始化、信号监听这些关键逻辑,全在 main 或 NewServer() 里。
立即学习“go语言免费学习笔记(深入)”;
- 重构入口:把
main()里的逻辑抽成server.NewServer()或cmd.Run(),接收*http.ServeMux、addr、context.Context - 测试中调用它并起 goroutine:
go srv.ListenAndServe(),再用标准http.Client发请求 - 采集覆盖率要用
go test -covermode=count -coverprofile=cover.out,配合-coverpkg=./...指定被测包范围 - 别信 “测试跑过了就行”,
go tool cover -func=cover.out看一眼main.go和中间件文件的覆盖率,低于 80% 就说明启动链路没被触达
真正难的从来不是写几个 t.Run,而是让测试进程和生产进程共享同一套初始化逻辑——否则你测的只是幻影。


















