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!")
}

功能特性

  1. 多种容器支持:支持多种数据库和服务的容器,如 MySQL、PostgreSQL、Redis、MongoDB 等
  2. 等待策略:提供多种等待容器就绪的策略(日志、HTTP、TCP 等)
  3. 资源清理:自动清理测试后创建的容器
  4. 网络支持:支持自定义容器网络配置
  5. 卷挂载:支持挂载主机目录到容器中

高级示例:使用 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

1 回复

更多关于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)
}

最佳实践

  1. 复用容器:对于耗时较长的容器,考虑在多个测试间复用,可以使用 testing.M 和全局变量。

  2. 并行测试:确保容器名称唯一,避免并行测试冲突:

    req := testcontainers.ContainerRequest{
        Name: fmt.Sprintf("container-%d", time.Now().UnixNano()),
        // ...
    }
    
  3. 资源清理:始终使用 defer 确保容器被正确清理。

  4. 等待策略:使用适当的等待策略确保容器真正准备好:

    WaitingFor: wait.ForAll(
        wait.ForLog("ready"),
        wait.ForListeningPort("8080/tcp"),
    ),
    
  5. 调试:如果测试失败,可以临时禁用容器清理以检查容器状态:

    // defer container.Terminate(ctx) // 注释掉以检查容器
    

testcontainers-go 是一个强大的工具,可以显著提高集成测试的可靠性和真实性。通过使用真实的依赖服务而不是模拟,你可以更有信心地验证代码的行为。

回到顶部