单元测试只验证函数逻辑,不接触数据库和网络;集成测试则需启动真实依赖(如PostgreSQL)验证整条链路。二者应通过构建标签隔离,且集成测试重在环境稳定与关键路径覆盖。

单元测试只测函数,不碰数据库和网络
Go 的单元测试本质是“逻辑快照”:它只验证一个函数在给定输入下是否返回预期输出,其他一概不管。这意味着 sql.Open、http.Get、文件读写等外部调用必须被替换或绕过。
- 常见错误:在
TestXxx函数里直接连真实 MySQL,导致测试失败不是因为逻辑错,而是因为数据库没启、密码错、表不存在 - 正确做法:用接口抽象依赖(如定义
DBClient接口),测试时传入 mock 实现;或用io.Discard、httptest.NewServer等标准库工具隔离 I/O - 性能影响:单个单元测试通常在毫秒级完成;100 个单元测试跑完一般不超过 1 秒
集成测试要启动真实依赖,比如 Docker 化的 PostgreSQL
集成测试的目标不是“函数对不对”,而是“这一整条链路通不通”——比如 HTTP handler → service → repository → PostgreSQL → 返回 JSON。它必须用真实组件,否则就不是集成。
- 使用场景:验证迁移脚本是否建表成功、gRPC client 能否连上 server、API 是否返回符合 OpenAPI 规范的字段
- 关键技巧:用
//go:build integration构建标签隔离,避免go test默认执行它们;CI 中用go test -tags=integration -timeout=60s控制资源消耗 - 容易踩的坑:本地测试连了生产数据库(尤其当
DB_URL从环境变量读取却没设默认值);忘记defer db.Close()导致连接泄漏,后续测试卡住
go test -cover 只反映单元测试覆盖率,对集成测试基本无效
Go 的覆盖率统计基于源码行是否被执行,而集成测试中大量逻辑运行在外部服务里(比如 SQL 查询本身不经过 Go 源码),所以 go test -cover 数值会严重失真。
- 如果你跑
go test -cover得到 85%,那只是“你写的 Go 代码里有 85% 的行被单元测试触达”,不代表业务流程覆盖充分 - 真正需要关注的是:核心路径有没有至少一个集成测试兜底?比如 “用户注册 → 发送邮件 → 登录成功” 这个端到端流程是否被某个
TestUserRegistrationFlow覆盖? - 不要为了提升
-cover数字而给日志打印、panic 分支写无意义的单元测试;优先保障边界条件(空输入、超长字符串、数据库唯一约束冲突)有对应集成验证
别把 TestMain 当 setup,它不解决并发测试的资源竞争
func TestMain(m *testing.M) 确实适合做全局初始化(如启动一次 Docker 容器),但它不能保证每个 TestXxx 是串行执行的——Go 测试默认并发运行,多个测试同时操作同一个数据库表就会出问题。
- 典型现象:
TestCreateUser和TestDeleteUser都用users表,但一个刚 INSERT,另一个已 TRUNCATE,结果随机失败 - 解决方案只有两个:要么每个测试用独立 schema 或前缀表名(如
users_test_123);要么显式加t.Parallel()并禁用并发(go test -p 1),但后者会让集成测试变慢数倍 - 更务实的做法:集成测试只保留关键 happy path,其余用单元测试 + mock 覆盖分支逻辑
15.3 而非 latest、所有时间相关逻辑用 clock.Now() 接口注入,这些细节比测试语法重要得多。










