01 从“环境地狱”说起
还在用公共测试库?
- 张三跑完订单脚本,李四的库存断言就挂红;
- 新人入职第一天,光装依赖就花掉 4 个小时;
- CI 里 MySQL 端口 3306 被占用,整条流水线随机失败。
Docker in Test(下文简称 DiT)就是为此诞生的一套方法论:把外部依赖全部容器化,在测试生命周期内按需拉起、用完即焚。它不是一个工具,而是一种“干净、可重复、可编排”的测试哲学。
02 什么是 Docker in Test?
一句话定义:
在单元/集成/端到端测试的
setup → run → teardown
全阶段,用 Docker 容器提供与生产一致、完全隔离、可版本化的外部服务。
核心特征:
- Ephemeral:容器生命周期 = 测试生命周期;
- Declarative:镜像版本、环境变量、初始化脚本全部代码化;
- Composable:通过 compose 或 testcontainers 串起多服务拓扑;
- Language-agnostic:Java/Go/Python/Node 均有成熟 SDK。
03 三大落地模式
模式 | 适用场景 | 代表工具 | 示例 |
---|---|---|---|
1️⃣ 手工脚本 | 本地调试、一次性验证 | docker run |
docker run -d --name mysql_test -e MYSQL_ROOT_PASSWORD=root -p 3307:3306 mysql:8.0 |
2️⃣ Compose 文件 | 集成测试、微服务链路 | docker-compose.test.yml |
一键拉起 app + db + redis |
3️⃣ Testcontainers | 单元/集成测试、CI | Java/Go/Python SDK | mysql := testcontainers.RunContainer(ctx, "mysql:8.0", ...) |
04 实战:Go 项目 30 秒启动 MySQL
以 ygggo_mysql 框架为例,演示 DiT 如何让每个测试获得独立数据库:
// +build integration
package dao_test
import (
"context"
"testing"
"github.com/testcontainers/testcontainers-go/modules/mysql"
"github.com/yggai/ygggo_mysql"
)
func TestUserCreate(t *testing.T) {
ctx := context.Background()
// 1. 拉起容器
mysqlC, _ := mysql.RunContainer(ctx,
testcontainers.WithImage("mysql:8.0"),
mysql.WithDatabase("testdb"),
mysql.WithUsername("root"),
mysql.WithPassword("root"),
)
defer mysqlC.Terminate(ctx)
// 2. 获取 DSN
dsn, _ := mysqlC.DSN(ctx)
// 3. 连接并执行测试
db := ygggo_mysql.MustNew(dsn)
_, err := db.Exec("CREATE TABLE user(id INT PRIMARY KEY, name VARCHAR(32))")
if err != nil {
t.Fatalf("create table: %v", err)
}
}
- 每个
go test -tags=integration
都会在 32768+ 端口起一个新 MySQL,跑完后自动销毁; - 并发安全,CI 并行任务互不干扰;
- 镜像版本锁定在代码库,升级只需改一行。
05 CI/CD 中的黄金链路
GitHub Actions 片段:
- name: Run integration tests
uses: docker/setup-buildx-action@v3
- run: go test -tags=integration ./...
无需在 Runner 里预装 MySQL;
无需清理端口、数据目录;
测试失败时,Actions 页面可直接下载容器日志和数据库快照,秒级复现。
06 不止 MySQL:DiT 全景地图
- 消息队列:Kafka、RabbitMQ 容器化,生产/消费测试一次到位;
- 搜索引擎:Elasticsearch 7.x/8.x 多版本并行验证;
- 云组件:LocalStack 模拟 AWS S3/SQS,离线跑通云端集成;
- 浏览器:Selenium Grid on Docker,Chrome/Firefox 自动扩缩容;
- 性能:JMeter/K6 分布式负载测试,容器即节点。
07 避坑指南
坑 | 对策 |
---|---|
镜像体积大,CI 拉取慢 | 使用 多阶段构建 + Registry 缓存 |
随机端口导致配置复杂 | testcontainers 自动生成 DSN/连接串 |
数据量过大,启动慢 | 用 Docker Volume Cache 或 轻量级替代品(Tidb-Lite、H2) |
Windows/Mac 宿主机文件权限 | 统一用 Linux CI 环境 或 golang:1.22-alpine 镜像 |
08 结语:把环境也纳入版本控制
Docker in Test 的最终目标,是让“环境”像代码一样可 review、可回滚、可分支。
当你把 Dockerfile
、compose.yml
、testcontainers
代码一起 push 时,测试就不再是开发流程中的瓶颈,而是一张随时可拉的“绿色安全网”。
测试环境不再是运维的锅,而是架构师与 QA 共同编写的 基础设施即测试。
09 延伸阅读
- Testcontainers 官方文档:https://testcontainers.com
- 《Docker in Practice 第二版》第 9 章:Testing with Docker
- CNCF Landscape:持续交付 & 测试工具全景图
欢迎在评论区分享你的 DiT 踩坑或最佳实践,一起把“环境地狱”送进历史。