postgresql容器连接必须用container.host(ctx)和container.mappedport(ctx, "5432/tcp")获取动态地址与端口,并加?sslmode=disable;就绪检测用wait.forlisteningport("5432/tcp")而非日志;每个测试独立启停容器并立即defer terminate(ctx)。

PostgreSQL容器连不上?别硬写 localhost:5432
testcontainers-go 启的 PostgreSQL 容器,IP 和端口是动态分配的,localhost:5432 在宿主机上可能通,在 Go 测试代码里直接写死就必连失败。
- 必须用
container.Host(ctx)拿宿主机可访问的地址(Linux 是172.17.0.1类,Mac/Windows 是 Docker Desktop 虚拟机 IP) - 必须用
container.MappedPort(ctx, "5432/tcp")拿实际映射到宿主机的端口号,不是镜像默认端口 - 连接字符串末尾务必加
?sslmode=disable,否则sql.Open会卡在 TLS 握手——testcontainers 启的 PG 默认没配证书 - 常见错误现象:
dial tcp [::1]:5432: connect: connection refused或failed to open database: pq: SSL is not enabled on the server
WaitingFor 选 ForListeningPort,别信日志关键词
PostgreSQL 启动日志不稳定,ForLog("database system is ready to accept connections") 在某些镜像版本(如 postgres:15-alpine)里可能不出现、或出现多次、或被缓冲延迟,导致容器“假就绪”。
- 优先用
wait.ForListeningPort("5432/tcp"),它真正探测端口是否可 bind+accept - 配合
WithStartupTimeout(60 * time.Second)防卡死,WithPollInterval(2 * time.Second)避免高频轮询 - 别依赖镜像文档写的“默认端口”——
timescale/timescaledb:latest可能同时暴露5432/tcp和8080/tcp,漏写一个就等不到
测试结束容器没删?defer container.Terminate(ctx) 必须放对位置
CI 跑几次磁盘就爆满、本地再跑测试报 port already allocated,90% 是因为 Terminate 没执行,或执行得太晚。
- 不能只在 setup 函数里
defer—— 如果测试 panic,这个 defer 不触发 - 不能先调
Stop()再Terminate()——Terminate()内部会先检查状态,已 stop 的容器直接返回,等于白写 - 最稳妥做法:每个
TestXxx函数第一行启容器,第二行立刻defer container.Terminate(ctx) - CI 中建议加兜底清理:
docker system prune -f --filter "until=30m",防 panic 导致清理遗漏
并行测试多个 PostgreSQL?别共用同一个容器
Go 测试默认并行(go test -p),如果所有 TestXxx 共享一个全局 pgContainer,数据会互相污染、事务隔离失效、甚至 DDL 冲突(比如两个测试同时 CREATE TABLE users)。
- 每个测试函数应独立启停容器,哪怕慢一点,也比结果不可靠强
- 如果真要复用(比如初始化耗时太久),得用
TestMain统一管理生命周期,并确保数据库名、schema、表前缀都按测试名隔离 - 轻量替代方案:用
postgres:15-alpine替代postgres:latest,启动快 3–5 秒,减少并行等待时间
容器不是黑盒,它和你的测试进程一样要精确控制生命周期;端口、就绪、清理,三处任意一个没对齐,测试就从“自动化”退化成“手动碰运气”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











