golang自动化测试容器依赖管理插件库testcontainers-go的使用
Golang自动化测试容器依赖管理插件库testcontainers-go的使用
Testcontainers for Go 是一个 Go 包,可以简化自动化集成/冒烟测试中基于容器的依赖项的创建和清理。它提供了简洁易用的 API,使开发人员能够以编程方式定义测试中应该运行的容器,并在测试完成后清理这些资源。
快速开始
首先需要将 testcontainers-go 添加到您的 Go 项目中:
import (
"context"
"testing"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
基本示例
下面是一个使用 testcontainers-go 启动 Redis 容器并在测试中使用的完整示例:
package main
import (
"context"
"fmt"
"testing"
"github.com/go-redis/redis/v8"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
func TestRedis(t *testing.T) {
// 1. 创建 Redis 容器请求
req := testcontainers.ContainerRequest{
Image: "redis:latest", // 使用官方 Redis 镜像
ExposedPorts: []string{"6379/tcp"}, // 暴露 Redis 默认端口
WaitingFor: wait.ForLog("Ready to accept connections"), // 等待容器启动完成
}
// 2. 启动 Redis 容器
redisC, err := testcontainers.GenericContainer(context.Background(),
testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
t.Fatal(err)
}
// 3. 确保测试完成后容器会被清理
defer func() {
if err := redisC.Terminate(context.Background()); err != nil {
t.Fatalf("failed to terminate container: %s", err)
}
}()
// 4. 获取容器的主机和端口
endpoint, err := redisC.Endpoint(context.Background(), "")
if err != nil {
t.Fatal(err)
}
// 5. 创建 Redis 客户端并测试连接
rdb := redis.NewClient(&redis.Options{
Addr: endpoint,
})
// 测试 Redis 操作
err = rdb.Set(context.Background(), "key", "value", 0).Err()
if err != nil {
t.Fatal(err)
}
val, err := rdb.Get(context.Background(), "key").Result()
if err != nil {
t.Fatal(err)
}
if val != "value" {
t.Errorf("expected value to be 'value', got '%s'", val)
}
fmt.Println("Redis test passed successfully!")
}
功能特性
- 多种容器支持:支持多种数据库和服务的容器,如 MySQL、PostgreSQL、Redis、MongoDB 等
- 等待策略:提供多种等待容器就绪的策略(日志、HTTP、TCP 等)
- 资源清理:自动清理测试后创建的容器
- 网络支持:支持自定义容器网络配置
- 卷挂载:支持挂载主机目录到容器中
高级示例:使用 MySQL 容器
func TestMySQL(t *testing.T) {
// 1. 创建 MySQL 容器请求
req := testcontainers.ContainerRequest{
Image: "mysql:8.0",
ExposedPorts: []string{"3306/tcp"},
Env: map[string]string{
"MYSQL_ROOT_PASSWORD": "password",
"MYSQL_DATABASE": "testdb",
},
WaitingFor: wait.ForLog("port: 3306 MySQL Community Server"),
}
// 2. 启动 MySQL 容器
mysqlC, err := testcontainers.GenericContainer(context.Background(),
testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
t.Fatal(err)
}
defer mysqlC.Terminate(context.Background())
// 3. 获取连接信息
host, err := mysqlC.Host(context.Background())
if err != nil {
t.Fatal(err)
}
port, err := mysqlC.MappedPort(context.Background(), "3306")
if err != nil {
t.Fatal(err)
}
// 4. 连接 MySQL 并测试
dsn := fmt.Sprintf("root:password@tcp(%s:%s)/testdb?charset=utf8mb4&parseTime=True&loc=Local",
host, port.Port())
db, err := sql.Open("mysql", dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
// 创建表
_, err = db.Exec("CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255))")
if err != nil {
t.Fatal(err)
}
// 插入数据
_, err = db.Exec("INSERT INTO users (name) VALUES (?)", "testuser")
if err != nil {
t.Fatal(err)
}
// 查询数据
var count int
err = db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
if err != nil {
t.Fatal(err)
}
if count != 1 {
t.Errorf("expected 1 user, got %d", count)
}
}
Testcontainers for Go 是一个强大的工具,可以简化 Go 项目中的集成测试,特别是当测试依赖于外部服务时。它能够确保测试环境的一致性,并自动管理测试资源的生命周期。
更多关于golang自动化测试容器依赖管理插件库testcontainers-go的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang自动化测试容器依赖管理插件库testcontainers-go的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
testcontainers-go 使用指南
testcontainers-go 是一个用于 Go 的测试库,它允许你在测试中轻松启动和管理 Docker 容器作为依赖项。这对于集成测试特别有用,可以确保你的代码与真实服务(如数据库、消息队列等)正确交互。
安装
首先安装 testcontainers-go 库:
go get github.com/testcontainers/testcontainers-go
基本用法
1. 启动一个简单的容器
package main
import (
"context"
"fmt"
"testing"
"time"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
func TestWithRedis(t *testing.T) {
// 创建 Redis 容器请求
ctx := context.Background()
req := testcontainers.ContainerRequest{
Image: "redis:latest",
ExposedPorts: []string{"6379/tcp"},
WaitingFor: wait.ForLog("Ready to accept connections").WithStartupTimeout(30 * time.Second),
}
// 启动容器
redisC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
t.Fatal(err)
}
// 测试结束后清理容器
defer func() {
if err := redisC.Terminate(ctx); err != nil {
t.Fatalf("failed to terminate container: %s", err)
}
}()
// 获取容器IP和端口
endpoint, err := redisC.Endpoint(ctx, "")
if err != nil {
t.Fatal(err)
}
fmt.Println("Redis endpoint:", endpoint)
// 在这里可以使用 endpoint 连接到 Redis 进行测试
}
2. 使用预定义的模块
testcontainers-go 提供了一些常见服务的预定义模块:
package main
import (
"context"
"testing"
"github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/testcontainers/testcontainers-go/wait"
)
func TestWithPostgres(t *testing.T) {
ctx := context.Background()
// 使用 Postgres 模块
pgContainer, err := postgres.RunContainer(ctx,
postgres.WithDatabase("test-db"),
postgres.WithUsername("postgres"),
postgres.WithPassword("postgres"),
testcontainers.WithWaitStrategy(
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).
WithStartupTimeout(30*time.Second)),
)
if err != nil {
t.Fatal(err)
}
defer pgContainer.Terminate(ctx)
// 获取连接字符串
connStr, err := pgContainer.ConnectionString(ctx)
if err != nil {
t.Fatal(err)
}
// 使用 connStr 连接到数据库进行测试
t.Log("Postgres connection string:", connStr)
}
高级用法
1. 自定义容器配置
func TestWithCustomContainer(t *testing.T) {
ctx := context.Background()
req := testcontainers.ContainerRequest{
Image: "nginx:latest",
ExposedPorts: []string{"80/tcp"},
WaitingFor: wait.ForHTTP("/").WithPort("80"),
Env: map[string]string{
"ENV_VAR": "value",
},
Mounts: testcontainers.ContainerMounts{
testcontainers.BindMount("/host/path", "/container/path"),
},
}
nginxC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
t.Fatal(err)
}
defer nginxC.Terminate(ctx)
// 获取映射端口
port, err := nginxC.MappedPort(ctx, "80")
if err != nil {
t.Fatal(err)
}
t.Logf("Nginx is running on port %s", port.Port())
}
2. 使用容器网络
func TestWithNetwork(t *testing.T) {
ctx := context.Background()
// 创建自定义网络
networkName := "test-network"
net, err := testcontainers.GenericNetwork(ctx, testcontainers.GenericNetworkRequest{
NetworkRequest: testcontainers.NetworkRequest{
Name: networkName,
CheckDuplicate: true,
},
})
if err != nil {
t.Fatal(err)
}
defer net.Remove(ctx)
// 在网络上启动容器
req := testcontainers.ContainerRequest{
Image: "alpine",
Cmd: []string{"tail", "-f", "/dev/null"}, // 保持容器运行
Networks: []string{networkName},
}
alpineC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
t.Fatal(err)
}
defer alpineC.Terminate(ctx)
}
最佳实践
-
复用容器:对于耗时较长的容器,考虑在多个测试间复用,可以使用
testing.M
和全局变量。 -
并行测试:确保容器名称唯一,避免并行测试冲突:
req := testcontainers.ContainerRequest{ Name: fmt.Sprintf("container-%d", time.Now().UnixNano()), // ... }
-
资源清理:始终使用
defer
确保容器被正确清理。 -
等待策略:使用适当的等待策略确保容器真正准备好:
WaitingFor: wait.ForAll( wait.ForLog("ready"), wait.ForListeningPort("8080/tcp"), ),
-
调试:如果测试失败,可以临时禁用容器清理以检查容器状态:
// defer container.Terminate(ctx) // 注释掉以检查容器
testcontainers-go 是一个强大的工具,可以显著提高集成测试的可靠性和真实性。通过使用真实的依赖服务而不是模拟,你可以更有信心地验证代码的行为。