Go与Docker集成测试:完整指南
集成测试是开发可靠Go应用程序的关键阶段。与单元测试不同,集成测试检查组件之间的交互:数据库、缓存、外部API。Docker可以隔离测试环境,使测试可重现且独立于主机。在本文中,我们将探讨如何为Go应用程序配置Docker集成测试——从简单容器到使用Testcontainers和CI/CD的高级场景。
1. 为什么在Go集成测试中使用Docker?
Go应用程序通常与PostgreSQL、Redis、MongoDB或其他服务配合使用。在每个开发机器和CI服务器上手动配置这些依赖项是导致错误的途径。Docker解决了这个问题:
- 可重现性 — 本地和CI环境一致。
- 隔离性 — 测试不会污染主机系统。
- 速度 — 容器在几秒内启动。
- 并行性 — 可以同时运行多个隔离的测试。
考虑一个基本示例:测试一个将数据保存到PostgreSQL的函数。
2. 使用Docker Compose配置测试环境
为测试数据库创建docker-compose.test.yml:
version: '3.8'services: postgres_test: image: postgres:16-alpine environment: POSTGRES_USER: test POSTGRES_PASSWORD: test POSTGRES_DB: testdb ports: - "5433:5432" # 使用非标准端口以避免干扰本地数据库 tmpfs: /var/lib/postgresql/data # 数据存储在内存中以提升速度在Go测试中,通过os/exec启动容器或使用testcontainers-go库。我们将展示两种方法。
2.1. 从测试中手动启动Docker Compose
// main_test.gopackage main
import ( "context" "fmt" "os/exec" "testing" "time")
func setupTestDB(t *testing.T) string { t.Helper() // 启动容器 cmd := exec.Command("docker-compose", "-f", "docker-compose.test.yml", "up", "-d") if err := cmd.Run(); err != nil { t.Fatalf("failed to start docker-compose: %v", err) } // 等待PostgreSQL就绪 time.Sleep(3 * time.Second) return "postgres://test:test@localhost:5433/testdb?sslmode=disable"}
func TestSaveUser(t *testing.T) { dsn := setupTestDB(t) db, err := sql.Open("postgres", dsn) if err != nil { t.Fatal(err) } defer db.Close() // ... 测试代码}缺点:手动sleep,并行测试复杂。最好使用Testcontainers。
2.2. Go的Testcontainers
安装库:
go get github.com/testcontainers/testcontainers-go自动管理容器的示例:
// repository_test.gopackage repository
import ( "context" "testing" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" "github.com/jackc/pgx/v5/pgxpool")
func TestUserRepository_Save(t *testing.T) { ctx := context.Background()
// 请求PostgreSQL容器 req := testcontainers.ContainerRequest{ Image: "postgres:16-alpine", ExposedPorts: []string{"5432/tcp"}, Env: map[string]string{ "POSTGRES_USER": "test", "POSTGRES_PASSWORD": "test", "POSTGRES_DB": "testdb", }, WaitingFor: wait.ForLog("database system is ready to accept connections"), }
postgres, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req