You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

testcontainers-go不支持绑定卷时,如何用Go测试Docker容器文件输出?

问题翻译

我用Go开发了一款CLI应用,能从源码文件和行内注释生成AsciiDoc文件,该应用以Docker镜像形式分发。测试阶段计划结合Godog做BDD测试,需要启动带测试数据的Docker容器运行这个应用,再对生成的AsciiDoc文件做断言验证。原本打算用testcontainers-go实现,但看其官方文档说该工具不支持绑定卷。因为容器执行完命令就会终止,没法查看或提取容器内的文件,所以希望用类似docker run ... --volume some/path:some/path ...的绑定卷方式,让容器正常终止后,还能在宿主机文件系统上对结果做断言。请问能不能通过testcontainers-go实现这个需求?如果不能,还有哪些用Go测试Docker容器文件输出的替代方案?


解答

一、testcontainers-go 完全支持绑定卷

你看到的文档信息可能是旧版本或表述偏差,testcontainers-go 早就支持通过绑定卷实现宿主机与容器的目录映射。即使容器执行完成后终止,宿主机挂载目录依然会保留容器生成的文件,完全满足你的断言需求。

示例代码如下:

import (
    "context"
    "os"
    "path/filepath"
    "strings"
    "testing"

    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/wait"
)

func TestCLIAppAsciiDocOutput(t *testing.T) {
    ctx := context.Background()

    // 创建临时目录用于存放生成的AsciiDoc文件
    outputDir, err := os.MkdirTemp("", "cli-test-output")
    if err != nil {
        t.Fatal(err)
    }
    defer os.RemoveAll(outputDir)

    // 获取宿主机测试数据目录的绝对路径
    testDataDir, err := filepath.Abs("./test-data")
    if err != nil {
        t.Fatal(err)
    }

    // 定义容器请求配置
    containerReq := testcontainers.ContainerRequest{
        Image: "your-cli-app-image:latest",
        Cmd:   []string{"generate", "/input", "/output"}, // 你的CLI应用执行命令
        Mounts: testcontainers.Mounts(
            // 绑定测试数据目录到容器内的/input路径
            testcontainers.BindMount(testDataDir, "/input"),
            // 绑定宿主机临时目录到容器内的/output路径
            testcontainers.BindMount(outputDir, "/output"),
        ),
        WaitingFor: wait.ForExit(), // 等待容器执行完成后退出
    }

    // 创建并启动容器
    container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
        ContainerRequest: containerReq,
        Started:          true,
    })
    if err != nil {
        t.Fatal(err)
    }
    defer container.Terminate(ctx)

    // 读取宿主机临时目录中的生成文件,执行断言
    generatedFile := filepath.Join(outputDir, "result.adoc")
    content, err := os.ReadFile(generatedFile)
    if err != nil {
        t.Fatalf("Failed to read generated file: %v", err)
    }

    // 示例断言:检查生成内容是否包含预期文本
    if !strings.Contains(string(content), "Expected AsciiDoc Section") {
        t.Error("Generated AsciiDoc does not match expected content")
    }
}

二、替代方案

如果因特殊场景不想使用testcontainers-go,还有两种常用方案:

1. 直接调用Docker CLI命令

用Go的os/exec包执行原生docker run命令,带上--volume参数实现目录绑定,容器终止后直接读取宿主机挂载目录的文件做断言。这种方式完全模拟手动操作,适合简单测试场景。

示例代码:

import (
    "os"
    "os/exec"
    "path/filepath"
    "strings"
    "testing"
)

func TestCLIWithDockerCLI(t *testing.T) {
    outputDir, err := os.MkdirTemp("", "docker-cli-test")
    if err != nil {
        t.Fatal(err)
    }
    defer os.RemoveAll(outputDir)

    testDataDir, _ := filepath.Abs("./test-data")

    // 执行docker run命令,绑定卷并运行CLI应用
    cmd := exec.Command("docker", "run", "--rm",
        "-v", testDataDir+":/input",
        "-v", outputDir+":/output",
        "your-cli-app-image:latest",
        "generate", "/input", "/output")

    if err := cmd.Run(); err != nil {
        t.Fatalf("Docker run failed: %v", err)
    }

    // 读取生成文件并断言
    generatedFile := filepath.Join(outputDir, "result.adoc")
    content, _ := os.ReadFile(generatedFile)
    if !strings.Contains(string(content), "Expected Content") {
        t.Error("Generated AsciiDoc is invalid")
    }
}
2. 使用Docker Go SDK

通过官方Docker SDK for Go直接操作容器,配置绑定卷、启动容器并等待退出,之后读取宿主机挂载的文件。这种方式比调用CLI更灵活,能更好地集成到Go测试流程中。

示例代码片段:

import (
    "context"
    "os"
    "path/filepath"
    "strings"
    "testing"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/api/types/container"
    "github.com/docker/docker/client"
)

func TestCLIWithDockerSDK(t *testing.T) {
    ctx := context.Background()
    cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
    if err != nil {
        t.Fatal(err)
    }
    defer cli.Close()

    outputDir, err := os.MkdirTemp("", "docker-sdk-test")
    if err != nil {
        t.Fatal(err)
    }
    defer os.RemoveAll(outputDir)

    testDataDir, _ := filepath.Abs("./test-data")

    // 配置容器主机绑定卷
    hostConfig := &container.HostConfig{
        Binds: []string{
            testDataDir + ":/input",
            outputDir + ":/output",
        },
    }

    // 创建容器
    resp, err := cli.ContainerCreate(ctx, &container.Config{
        Image: "your-cli-app-image:latest",
        Cmd:   []string{"generate", "/input", "/output"},
    }, hostConfig, nil, nil, "")
    if err != nil {
        t.Fatal(err)
    }
    defer cli.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})

    // 启动容器
    if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
        t.Fatal(err)
    }

    // 等待容器执行完成退出
    _, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
    if err := <-errCh; err != nil {
        t.Fatal(err)
    }

    // 读取生成文件并断言
    generatedFile := filepath.Join(outputDir, "result.adoc")
    content, _ := os.ReadFile(generatedFile)
    if !strings.Contains(string(content), "Expected AsciiDoc Content") {
        t.Error("Generated file does not meet requirements")
    }
}

内容的提问来源于stack exchange,提问作者Sebastian Sommerfeld

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 03:22:28