Golang使用Docker进行集成测试的示例详解

 更新时间:2023年07月27日 08:58:14   作者:江湖十年  
集成测试需要解决外部依赖问题,如 MySQL、Redis、网络等依赖,本文就来聊聊 Go 程序如何使用 Docker 来解决集成测试中外部依赖问题吧

实践表明,有时程序中某个模块虽然可以单独工作,但是并不能保证多个模块组装起来也可以同时工作,于是就有了集成测试。

集成测试需要解决外部依赖问题,如 MySQL、Redis、网络等依赖,解决这些外部依赖问题最佳实践则是使用 Docker,本文就来聊聊 Go 程序如何使用 Docker 来解决集成测试中外部依赖问题。

登录程序示例

在 Web 开发中,登录需求是一个较为常见的功能。所以,本文就以登录程序为例,讲解使用 Docker 启动 Redis 进行集成测试。

登录程序如下:

func Login(mobile, smsCode string, rdb *redis.Client) (string, error) {
	ctx := context.Background()
	// 查找验证码
	captcha, err := GetSmsCaptchaFromRedis(ctx, rdb, mobile)
	if err != nil {
		if err == redis.Nil {
			return "", fmt.Errorf("invalid sms code or expired")
		}
		return "", err
	}
	if captcha != smsCode {
		return "", fmt.Errorf("invalid sms code")
	}
	token, _ := GenerateToken(32)
	err = SetAuthTokenToRedis(ctx, rdb, token, mobile)
	if err != nil {
		return "", err
	}
	return token, nil
}

可以通过如下方式获取 Redis 客户端对象 rdb

import "github.com/redis/go-redis/v9"
func NewRedisClient() *redis.Client {
	return redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})
}

生成随机 token 的函数定义如下:

var GenerateToken = func(length int) (string, error) {
	token := make([]byte, length)
	_, err := rand.Read(token)
	if err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(token)[:length], nil
}

本程序提供了如下几个操作 Reids 的函数:

var (
	smsCaptchaExpire    = 5 * time.Minute
	smsCaptchaKeyPrefix = "sms:captcha:%s"
	authTokenExpire    = 24 * time.Hour
	authTokenKeyPrefix = "auth:token:%s"
)
func SetSmsCaptchaToRedis(ctx context.Context, redis *redis.Client, mobile, captcha string) error {
	key := fmt.Sprintf(smsCaptchaKeyPrefix, mobile)
	return redis.Set(ctx, key, captcha, smsCaptchaExpire).Err()
}
func GetSmsCaptchaFromRedis(ctx context.Context, redis *redis.Client, mobile string) (string, error) {
	key := fmt.Sprintf(smsCaptchaKeyPrefix, mobile)
	return redis.Get(ctx, key).Result()
}
func DeleteSmsCaptchaFromRedis(ctx context.Context, redis *redis.Client, mobile string) error {
	key := fmt.Sprintf(smsCaptchaKeyPrefix, mobile)
	return redis.Del(ctx, key).Err()
}
func SetAuthTokenToRedis(ctx context.Context, redis *redis.Client, token, mobile string) error {
	key := fmt.Sprintf(authTokenKeyPrefix, token)
	return redis.Set(ctx, key, mobile, authTokenExpire).Err()
}
func GetAuthTokenFromRedis(ctx context.Context, redis *redis.Client, token string) (string, error) {
	key := fmt.Sprintf(authTokenKeyPrefix, token)
	return redis.Get(ctx, key).Result()
}
func DeleteAuthTokenFromRedis(ctx context.Context, redis *redis.Client, token string) error {
	key := fmt.Sprintf(authTokenKeyPrefix, token)
	return redis.Del(ctx, key).Err()
}

Login 函数用法如下:

func main() {
	rdb := NewRedisClient()
	token, err := Login("13800001111", "123456", rdb)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(token)
}

使用 Docker 进行集成测试

要想对 Login 函数进行集成测试,就需要解决 Reids 外部依赖问题。

在 Go 程序中,我们可以使用 testcontainers-go 这个包来解决,它可以让我们很方便的在 Docker 中启动 Reids 服务。

安装 testcontainers-go

$ go get github.com/testcontainers/testcontainers-go

我们可以在测试代码开始执行之前启动 Docker 容器来运行 Redis 服务,然后执行测试代码,最后测试代码执行完成后再停止并删除 Docker 容器。

可以定义一个 setup 函数用来准备 Docker 容器:

var rdbClient *redis.Client
func setup() func() {
	ctx := context.Background()
	req := testcontainers.ContainerRequest{
		Image:        "redis:6.0.20-alpine",
		ExposedPorts: []string{"6379/tcp"},
		WaitingFor:   wait.ForLog("Ready to accept connections"),
	}
	redisC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
		ContainerRequest: req,
		Started:          true,
	})
	if err != nil {
		panic(fmt.Sprintf("failed to start container: %s", err.Error()))
	}
	endpoint, err := redisC.Endpoint(ctx, "")
	if err != nil {
		panic(fmt.Sprintf("failed to get endpoint: %s", err.Error()))
	}
	rdbClient = redis.NewClient(&redis.Options{
		Addr: endpoint,
	})
	// 清理 Redis 容器
	return func() {
		if err := redisC.Terminate(ctx); err != nil {
			panic(fmt.Sprintf("failed to terminate container: %s", err.Error()))
		}
	}
}

可以发现使用 testcontainers-go 启动一个 Redis 容器非常简单,我们指定了 Docker 容器镜像为 redis:6.0.20-alpine,映射端口为 6379/tcp

Redis 容器启动后将实例化的 Redis 客户端保存到全局变量 rdbClient 中,方便在测试函数中使用,setup 函数最终返回一个 teardown 函数可以清理容器。

定义 TestMain 函数如下,作为测试程序的入口:

func TestMain(m *testing.M) {
	teardown := setup()
	code := m.Run()
	teardown()
	os.Exit(code)
}

为了测试 Login 函数,我们需要在 Reids 中准备一些测试数据,因为 Login 函数内部需要查询 Reids 中的验证码,所以可以定义一个 setupLogin 函数来实现:

func setupLogin(tb testing.TB) func(tb testing.TB) {
	// 准备测试数据
	err := SetSmsCaptchaToRedis(context.Background(), rdbClient, "18900001111", "123456")
	assert.NoError(tb, err)
	// 清理测试数据
	return func(tb testing.TB) {
		err := DeleteSmsCaptchaFromRedis(context.Background(), rdbClient, "18900001111")
		assert.NoError(tb, err)
		err = DeleteAuthTokenFromRedis(context.Background(), rdbClient, "token")
		assert.NoError(tb, err)
	}
}

setupLogin 函数返回 teardownLogin 函数用来清理 Redis 中的测试数据,防止当有多个测试函数时互相影响。

现在可以编写 Login 函数的测试代码了:

func TestLogin(t *testing.T) {
	teardownLogin := setupLogin(t)
	defer teardownLogin(t)
	// 测试登录成功情况
	token, err := Login("18900001111", "123456", rdbClient)
	assert.NoError(t, err)
	assert.Equal(t, "token", token)
	// 检查 Redis 中是否存在 token
	mobile, err := GetAuthTokenFromRedis(context.Background(), rdbClient, "token")
	assert.NoError(t, err)
	assert.Equal(t, "18900001111", mobile)
}

TestLogin 函数非常简单,这得益于前期的准备工作做的非常全面。

使用 go test 来执行测试函数:

$ go test -v                    
2023/07/26 20:48:12 github.com/testcontainers/testcontainers-go - Connected to docker: 
  Server Version: 20.10.21
  API Version: 1.41
  Operating System: Docker Desktop
  Total Memory: 7851 MB
2023/07/26 20:48:12 🐳 Creating container for image docker.io/testcontainers/ryuk:0.5.1
2023/07/26 20:48:12 ✅ Container created: a261dc723001
2023/07/26 20:48:12 🐳 Starting container: a261dc723001
2023/07/26 20:48:12 ✅ Container started: a261dc723001
2023/07/26 20:48:12 🚧 Waiting for container id a261dc723001 image: docker.io/testcontainers/ryuk:0.5.1. Waiting for: &{Port:8080/tcp timeout:<nil> PollInterval:100ms}
2023/07/26 20:48:13 🐳 Creating container for image redis:6.0.20-alpine
2023/07/26 20:48:13 ✅ Container created: 6420ead815a0
2023/07/26 20:48:13 🐳 Starting container: 6420ead815a0
2023/07/26 20:48:13 ✅ Container started: 6420ead815a0
2023/07/26 20:48:13 🚧 Waiting for container id 6420ead815a0 image: redis:6.0.20-alpine. Waiting for: &{timeout:<nil> Log:Ready to accept connections Occurrence:1 PollInterval:100ms}
=== RUN   TestLogin
--- PASS: TestLogin (0.01s)
PASS
2023/07/26 20:48:13 🐳 Terminating container: 6420ead815a0
2023/07/26 20:48:13 🚫 Container terminated: 6420ead815a0
ok      github.com/jianghushinian/test/db/redis 1.630s

测试通过。

总结

我们使用 testcontainers-go 包实现了在 Go 程序中启动一个 Docker 容器,以此解决了集成测试中依赖外部 Redis 问题。

可以发现,Docker 非常适合集成测试,使用 Dokcer 来辅助集成测试是 Go 应用程序集成测试的最佳实践。而完善的集成测试,可以确保应用程序的可靠性、可扩展性和可维护性。

以上就是Golang使用Docker进行集成测试的示例详解的详细内容,更多关于Go Docker集成测试的资料请关注脚本之家其它相关文章!

相关文章

  • Go语言defer语句的三种机制整理

    Go语言defer语句的三种机制整理

    在本篇文章里小编给大家分享的是一篇关于Go语言defer语句的三种机制整理,需要的朋友们学习下吧。
    2020-03-03
  • 一文带你揭秘Go中new()和make()函数的区别和用途

    一文带你揭秘Go中new()和make()函数的区别和用途

    Go(或 Golang)是一种现代、静态类型、编译型的编程语言,专为构建可扩展、并发和高效的软件而设计,它提供了各种内置的函数和特性,帮助开发人员编写简洁高效的代码,在本博客文章中,我们将探讨 new() 和 make() 函数之间的区别,了解何时以及如何有效地使用它们
    2023-10-10
  • Golang语言实现gRPC的具体使用

    Golang语言实现gRPC的具体使用

    本文主要介绍了Golang语言实现gRPC的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • 浅谈Gin框架中bind的使用

    浅谈Gin框架中bind的使用

    Gin框架中有bind函数,可以非常方便的将url的查询参数query parameter、http的Header,body中提交上来的数据格式,本文就详细的介绍Gin框架中bind的使用,感兴趣的可以了解一下
    2021-12-12
  • go语言实现十大常见的排序算法示例

    go语言实现十大常见的排序算法示例

    这篇文章主要为大家介绍了go语言实现十大常见的排序算法示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • Golang中由零值和gob库特性引起BUG解析

    Golang中由零值和gob库特性引起BUG解析

    这篇文章主要为大家介绍了Golang中由零值和gob库特性引起BUG解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • go cron定时任务的基本使用讲解

    go cron定时任务的基本使用讲解

    这篇文章主要为大家介绍了gocron定时任务的基本使用讲解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • gin session中间件使用及源码流程分析

    gin session中间件使用及源码流程分析

    这篇文章主要为大家介绍了gin session中间件使用及源码分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • go使用makefile脚本编译应用的方法小结

    go使用makefile脚本编译应用的方法小结

    makefile可以看作是make工具的脚本文件, 而make主要用来处理一系列命令。常用的比如用来编译和打包文件, 在C/C++的编译打包中应用最广泛了,这篇文章主要介绍了go使用makefile脚本编译应用,需要的朋友可以参考下
    2022-08-08
  • Go语言实现读取文件的方式总结

    Go语言实现读取文件的方式总结

    这篇文章主要为大家详细介绍了Go语言实现读取文件的几个方式,文中的示例代码讲解详细,对我们学习Go语言有一定的帮助,感兴趣的小伙伴可以收藏一下
    2023-04-04

最新评论