单元测试仅运行代码逻辑,不依赖外部服务,需用 interface + mock 替换数据库等调用;集成测试须启动真实依赖,通过 //go:build integration 和 -tags=integration 显式触发,并在测试函数中用 testing.short() 守门。

Go 单元测试和集成测试怎么分?看运行时依赖
单元测试只跑代码本身,不碰数据库、网络、文件系统;集成测试必须启动真实依赖(比如 PostgreSQL 容器或本地 Redis 实例)。两者混在一起跑,CI 会莫名其妙失败——不是代码错了,是环境没配好。
- 单元测试用
go test -short快速验证逻辑,所有外部调用都该被 interface + mock 替换(比如用github.com/stretchr/testify/mock) - 集成测试必须显式跳过
-short,且默认不执行:加//go:build integrationbuild tag,再用go test -tags=integration触发 - 常见错误:在单元测试里直接 new 一个
sql.DB并连本地localhost:5432——这已经不是单元测试,只是“跑得慢的集成测试”
build tags 怎么写才不会被 go test 忽略?
Go 的 build tag 不是注释,是编译期开关。写错位置、格式不对、空格多一个,go test -tags=xxx 就完全不起作用。
- 必须放在文件顶部,紧贴
package前,且前后各空一行://go:build integration<br><br>package dbtest
- 不能写成
// +build integration(旧语法,Go 1.17+ 已弃用,但部分老项目还在用,容易混淆) - 多个 tag 用空格连接:
//go:build integration && !unit;用&&和!时,整个表达式必须用英文括号包裹,否则解析失败 - 如果文件同时有
integration和unittag,又用了-tags=integration,但没排除unit,Go 会因为条件冲突跳过该文件
如何让集成测试只在 CI 里跑,本地开发默认跳过?
本地敲 go test 不该自动连 DB,否则开发者每次改个空行都要等 3 秒连不上 PostgreSQL 报错。关键在测试函数里做守门人,而不是只靠 build tag。
- 在集成测试函数开头加判断:
func TestUserCreate_Integration(t *testing.T) {<br> if testing.Short() {<br> t.Skip("skipping integration test")<br> }<br> // ... real DB setup<br>} - CI 脚本里明确启用:
go test -tags=integration -timeout=60s ./...,并确保环境变量DB_URL已就位 - 别依赖
init()或包级变量做连接初始化——它会在任何 import 该包时触发,哪怕只是go list都可能报错
为什么集成测试常出现 “address already in use” 或 “database is being accessed by other users”?
不是端口没释放,是多个测试并发抢同一个资源(比如共用 testdb 数据库名,或硬编码 :8080 端口)。
立即学习“go语言免费学习笔记(深入)”;
- 每个集成测试应使用随机数据库名:
dbname := fmt.Sprintf("test_%d", time.Now().UnixNano()),测试结束用defer dropDB(dbname) - HTTP 服务端口别写死,用
net.Listen("tcp", ":0")让系统分配空闲端口,再从l.Addr().(*net.TCPAddr).Port取值 - Docker 启的依赖(如
postgres:15)要加--rm和唯一 container name,避免上次失败残留导致端口占用
最麻烦的从来不是写测试,是让它们彼此不打架、不抢资源、不依赖固定环境。build tag 只是开关,真正的隔离得靠命名随机化、连接按需建、清理写进 defer。










