golang轻量级零依赖Docker测试容器插件库dft的使用

Golang轻量级零依赖Docker测试容器插件库dft的使用

🥼 DFT简介

DFT(Docker For Testing)是一个零依赖的docker命令包装器,完全基于标准库实现。

唯一要求:运行中的docker守护进程。

该包旨在用于各种测试设置,从本地测试到CI/CD流水线。其主要目标是减少对模拟对象(特别是数据库模拟)的需求,并降低测试所需的包数量。

容器可以通过端口、环境变量或[CMD]覆盖等选项启动。

👓 示例代码

下面是一个测试使用MongoDB支持的用户服务的完整示例:

package myawesomepkg_test

import (
	"context"
	"testing"
	"time"

	"my/awesome/pkg/repository"
	"my/awesome/pkg/user"

	"github.com/abecodes/dft"
)

func TestUserService(tt *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// 启动一个MongoDB容器
	ctr, err := dft.StartContainer(
		ctx,
		"mongo:7-jammy",
		dft.WithRandomPort(27017),
	)
	if err != nil {
		tt.Errorf("[dft.StartContainer] unexpected error: %v", err)
		tt.FailNow()
		return
	}

	// 等待数据库准备就绪
	err = ctr.WaitCmd(
		ctx,
		[]string{
			"mongosh",
			"--norc",
			"--quiet",
			"--host=localhost:27017",
			"--eval",
			"'db.getMongo()'",
		},
		func(stdOut string, stdErr string, code int) bool {
			tt.Logf("got:\n\tcode:%d\n\tout:%s\n\terr:%s\n", code, stdOut, stdErr)
			return code == 0
		},
		// 由于我们使用随机端口,需要在容器内执行命令
		dft.WithExecuteInsideContainer(true),
	)
	if err != nil {
		tt.Errorf("[dft.WaitCmd] wait error: %v", err)
		tt.FailNow()
		return
	}

	// 确保测试完成后清理容器
	defer func() {
		if ctr != nil {
			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			ctr.Stop(ctx)
			cancel()
		}
	}()

	// 获取容器暴露的端口地址
	addrs, ok := ctr.ExposedPortAddresses(27017)
	if !ok {
		tt.Error("[ctr.ExposedPortAddresses] did not return any addresses")
		tt.FailNow()
		return
	}

	// 获取数据库连接
	conn := createNewMongoClient(addrs[0])

	// 创建用户仓库
	userRepo := repository.User(conn)

	// 启动用户服务
	userSvc := user.New(userRepo)
	defer userSvc.Close()

	tt.Run(
		"it can store a new user in the database",
		func(t *testing.T) {
			// 创建新用户
			userSvc.New("awesome", "user")

			// 验证写入
			users := userRepo.GetAll()
			if len(users) != 1 &&
				users[0].FirstName != "awesome" &&
				users[0].LastName != "user" {
					t.Error("[userSvc.New] unable to create user")
					tt.FailNow()
					return
				}
		},
	)
}

🤖 API参考

StartContainer选项

选项 说明 示例
WithCmd 覆盖[CMD] WithCmd([]string{"--tlsCAFile", "/run/tls/ca.crt"})
WithEnvVar 设置容器内环境变量,可多次调用,相同key会覆盖 WithEnvVar("intent", "prod")
WithMount 挂载本地目录或文件,可多次调用 WithMount("./host/folder", "/target")
WithPort 将内部端口映射到特定主机端口 WithPort(27017,8080)
WithRandomPort 将内部端口映射到随机主机端口,使用ExposedPortsExposedPortAddresses获取实际端口 WithRandomPort(27017)

Wait选项

选项 说明 示例
WithExecuteInsideContainer 是否在容器内执行命令(默认false),适用于容器特有命令 WithExecuteInsideContainer(true)

这个库非常适合在Golang测试中快速启动和操作Docker容器,无需复杂的依赖关系,使测试更加真实可靠。


更多关于golang轻量级零依赖Docker测试容器插件库dft的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang轻量级零依赖Docker测试容器插件库dft的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang轻量级零依赖Docker测试容器插件库dft使用指南

dft (Docker For Test) 是一个轻量级、零依赖的Golang库,专门用于在单元测试和集成测试中管理Docker容器。它提供了简单的API来启动、停止和管理测试所需的容器服务。

主要特性

  • 零外部依赖(仅依赖标准库和Docker引擎API)
  • 轻量级实现
  • 支持容器生命周期管理
  • 自动清理测试容器
  • 支持自定义容器配置

安装

go get github.com/yourusername/dft

基本使用示例

1. 启动一个Redis测试容器

package main

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/yourusername/dft"
)

func TestWithRedis(t *testing.T) {
	// 创建dft客户端
	client, err := dft.NewClient()
	if err != nil {
		t.Fatalf("Failed to create client: %v", err)
	}
	defer client.Close()

	// 定义Redis容器配置
	redisCfg := &dft.ContainerConfig{
		Image:         "redis:alpine",
		ExposedPorts:  []string{"6379/tcp"},
		WaitingPorts:  []string{"6379"},
		WaitingPeriod: 5 * time.Second,
	}

	// 启动Redis容器
	redisContainer, err := client.RunContainer(context.Background(), redisCfg)
	if err != nil {
		t.Fatalf("Failed to start Redis container: %v", err)
	}
	defer redisContainer.Stop(context.Background())

	// 获取容器映射的端口
	port := redisContainer.GetMappedPort("6379")
	redisAddr := fmt.Sprintf("localhost:%s", port)

	// 这里可以使用redisAddr连接测试Redis
	t.Logf("Redis is running at %s", redisAddr)

	// 测试逻辑...
}

2. 启动PostgreSQL测试容器

func TestWithPostgreSQL(t *testing.T) {
	client, err := dft.NewClient()
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()

	pgCfg := &dft.ContainerConfig{
		Image:         "postgres:13-alpine",
		ExposedPorts:  []string{"5432/tcp"},
		Env:           []string{"POSTGRES_PASSWORD=testpass"},
		WaitingPorts:  []string{"5432"},
		WaitingPeriod: 10 * time.Second,
	}

	pgContainer, err := client.RunContainer(context.Background(), pgCfg)
	if err != nil {
		t.Fatal(err)
	}
	defer pgContainer.Stop(context.Background())

	port := pgContainer.GetMappedPort("5432")
	dsn := fmt.Sprintf("postgres://postgres:testpass@localhost:%s/postgres?sslmode=disable", port)

	t.Logf("PostgreSQL DSN: %s", dsn)
	
	// 测试数据库连接和逻辑...
}

高级功能

自定义容器配置

cfg := &dft.ContainerConfig{
	Image:        "my-custom-image",
	ExposedPorts: []string{"8080/tcp", "9090/tcp"},
	Env:          []string{"ENV_VAR=value", "ANOTHER_VAR=123"},
	Cmd:          []string{"myapp", "--debug"},
	// 等待特定日志输出才认为容器就绪
	WaitingFor: &dft.WaitForLog{
		Pattern: "Server started",
		Timeout: 30 * time.Second,
	},
}

多容器测试

func TestMultiContainer(t *testing.T) {
	client, err := dft.NewClient()
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()

	// 启动Redis
	redisCfg := &dft.ContainerConfig{
		Image:        "redis:alpine",
		ExposedPorts: []string{"6379/tcp"},
	}
	redis, err := client.RunContainer(context.Background(), redisCfg)
	if err != nil {
		t.Fatal(err)
	}
	defer redis.Stop(context.Background())

	// 启动应用容器,链接到Redis
	appCfg := &dft.ContainerConfig{
		Image:        "myapp:test",
		ExposedPorts: []string{"8080/tcp"},
		Env: []string{
			fmt.Sprintf("REDIS_URL=redis://%s:6379", redis.GetIP()),
		},
	}
	app, err := client.RunContainer(context.Background(), appCfg)
	if err != nil {
		t.Fatal(err)
	}
	defer app.Stop(context.Background())

	// 测试逻辑...
}

最佳实践

  1. 每个测试用例独立容器:为每个测试用例启动独立的容器,避免测试间相互影响
  2. 使用defer清理:确保测试结束后清理容器
  3. 合理设置等待时间:根据服务启动时间设置适当的WaitingPeriod
  4. 复用客户端:在测试套件级别创建dft客户端,而不是每个测试用例都创建

与类似库的比较

相比于testcontainers-go等库,dft的优势在于:

  • 更轻量,零外部依赖
  • 更简单的API设计
  • 更适合小型项目或简单测试场景

不足:

  • 功能相对较少
  • 社区支持较小

dft非常适合需要轻量级解决方案的测试场景,特别是当项目不希望引入过多依赖时。

回到顶部