与测试方法分开在 docker 应用程序中运行 docker 映像
Posted
技术标签:
【中文标题】与测试方法分开在 docker 应用程序中运行 docker 映像【英文标题】:Run docker images in a docker application separately from test method 【发布时间】:2022-01-23 09:19:28 【问题描述】:我有一个 docker 应用程序,它有两个容器。一个是 mysql,另一个是我创建的使用 Dockerfile 的自定义映像的一些逻辑代码。对于端到端测试,我希望在数据库中存储一些值,然后运行逻辑代码映像(Golang 中的逻辑)。这是我目前拥有的 docker-compose 文件:
version: '3'
networks:
docker-network:
driver: bridge
services:
database:
image: mysql
env_file:
- ./src/logic/environment-variables.env
ports:
- 3306:3306
healthcheck:
test: "mysql -uroot -p$$MYSQL_ROOT_PASSWORD $$MYSQL_DATABASE -e 'select 1'"
timeout: 20s
retries: 10
network:
docker-network
logic:
container_name: main-logic
build: ./src/logic/.
depends_on:
database:
condition: service_healthy
network:
docker-network
我不能作为一个整体运行这个应用程序,因为它会在数据库运行后立即运行主程序。相反,我想启动数据库,在其中存储一些值,然后运行逻辑映像。如何在测试方法中做到这一点?
考虑的方法: 与测试方法分开启动mysql映像,然后在其中存储值。然后启动逻辑映像并检查数据库以获取结果。有没有更好的方法或框架可以用于此?
【问题讨论】:
你想在你的 golang 应用程序启动之前正确初始化你的数据库吗? 是的。基本上需要数据库中有值,以便我可以测试逻辑图像中的代码 您可以为您的 MySQL 容器创建一个自定义 entrypoint.sh 脚本来运行迁移并构建/填充您的数据库...或者,如果您不使用任何 mysql 特定功能,请抽象驱动程序-type(IE:所以它可以是可变的)并使用磁盘上的 sqlite 数据库(IE:将其放在testdata/
目录中)并使用它来测试您的 DB 代码......或者只是模拟 DB 驱动程序
这能回答你的问题吗? Does docker-compose support init container?
@TheFool 这在某种程度上确实如此,但即使在实际情况下,问题也需要该功能,而不仅仅是用于测试。但是对于我的用例,我只需要它来进行测试,而不是在其他情况下运行代码时。如果我使用它,那么 docker-compose 在所有情况下对我来说都包含不必要的开销
【参考方案1】:
您需要的是数据库迁移。这应该如下工作:
在启动服务之前启动数据库实例。 将服务连接到数据库。 在 DB 上运行迁移。 继续执行服务。考虑一下:https://github.com/golang-migrate/migrate
【讨论】:
【参考方案2】:您可以完全按照您在问题中所说的做:启动数据库,手动加载种子数据,然后启动应用程序的其余部分。由于您的数据库已发布ports:
,您可以直接从主机连接到它,而无需做任何特殊操作。
docker-compose up -d database
mysql -h 127.0.0.1 < seed_data.sql
docker-compose up -d
@advayrajhansa 的answer 建议使用数据库迁移系统。如果这已内置到您的图像中,您可以将docker-compose run logic migrate ...
作为中间步骤。这会在您在 docker-compose.yml
文件中定义的容器上运行备用命令。
【讨论】:
我会试试这个。这是否意味着整个测试方法可以作为脚本运行并且不需要任何 go test funcs? 如果它是一种端到端集成测试,它向您的应用程序发出 HTTP 请求并评估输出,它不需要在容器内运行或使用与应用程序。但这似乎与“我如何为该测试设置初始数据”有点不同。【参考方案3】:对于您的方法:
-
启动 MySQL 映像。
将数据上传到数据库。
启动逻辑映像。
检查数据库以获取结果。
你可以:
使用 Makefile
里面有一个 sh 脚本,会一一执行所有步骤。
生成文件:
start_test:
docker-compose run -d database
# put here your data uploading script
docker-compose run -d logic
# put here your data database checking script
然后执行
$make start_test # execute all steps
使用Testcontainers-Go
Testcontainers GitHub
Testcontainers-Go 是一个 Go 包,可以轻松创建和清理基于容器的依赖项以进行自动化集成/冒烟测试。
它允许您执行 go 测试方法中的所有步骤。 对于你的情况,你会有这样的事情:
只是一个草稿代码来赶上这个想法:
package main
import (
"context"
"database/sql"
"fmt"
"github.com/pkg/errors"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
"log"
"testing"
)
var db *sql.DB
func TestIntegration(t *testing.T)
if testing.Short()
t.Skip("skipping integration test")
err := setupMySql()
if err != nil
t.Errorf("Test failed with error: %s", err)
err = setupData()
if err != nil
t.Errorf("Test failed with error: %s", err)
err = setupLogic()
if err != nil
t.Errorf("Test failed with error: %s", err)
err = checkResult()
if err != nil
t.Errorf("Test failed with error: %s", err)
func setupMySql() error
ctx := context.Background()
req := testcontainers.ContainerRequest
Image: "mysql:latest",
ExposedPorts: []string"3306/tcp", "33060/tcp",
Env: map[string]string
"MYSQL_ROOT_PASSWORD": "secret",
,
WaitingFor: wait.ForLog("port: 3306 MySQL Community Server - GPL"),
mysqlC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest
ContainerRequest: req,
Started: true,
)
defer func()
err := mysqlC.Terminate(ctx)
if err != nil
log.Fatal(err)
()
if err != nil
return errors.Wrap(err, "Failed to run test container")
host, err := mysqlC.Host(ctx)
p, err := mysqlC.MappedPort(ctx, "3306/tcp")
port := p.Int()
connectionString := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=skip-verify",
"root", "secret", host, port, "database")
db, err = sql.Open("mysql", connectionString)
defer func(db *sql.DB)
err := db.Close()
if err != nil
log.Fatal(err)
(db)
if err != nil
return errors.Wrap(err, "Failed to connect to db")
return nil
func setupData() error
// db.Query(), your code with uploading data
return nil
func setupLogic() error
// run your logic container
return nil
func checkResult() error
// db.Query(), your code with checking result
return nil
使用Dockertest
Dockertest 可帮助您以最少的工作量为您的 Go 测试启动临时 docker 映像。
与 Testcontainers-Go 相同,
只是一个草稿代码来赶上这个想法:
package main
import (
"database/sql"
"fmt"
"github.com/ory/dockertest/v3"
"github.com/pkg/errors"
"testing"
)
var db *sql.DB
func TestIntegration(t *testing.T)
if testing.Short()
t.Skip("skipping integration test")
err := setupMySql()
if err != nil
t.Errorf("Test failed with error: %s", err)
err = setupData()
if err != nil
t.Errorf("Test failed with error: %s", err)
err = setupLogic()
if err != nil
t.Errorf("Test failed with error: %s", err)
err = checkResult()
if err != nil
t.Errorf("Test failed with error: %s", err)
func setupMySql() error
// uses a sensible default on windows (tcp/http) and linux/osx (socket)
pool, err := dockertest.NewPool("")
if err != nil
return errors.Wrap(err, "Could not connect to docker")
// pulls an image, creates a container based on it and runs it
resource, err := pool.Run("mysql", "5.7", []string"MYSQL_ROOT_PASSWORD=secret")
if err != nil
return errors.Wrap(err, "Could not start resource")
// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
if err := pool.Retry(func() error
var err error
db, err = sql.Open("mysql", fmt.Sprintf("root:secret@(localhost:%s)/mysql", resource.GetPort("3306/tcp")))
if err != nil
return err
return db.Ping()
); err != nil
return errors.Wrap(err, "Could not connect to database")
if err := pool.Purge(resource); err != nil
return errors.Wrap(err, "Could not purge resource")
return nil
func setupData() error
// db.Query(), your code with uploading data
return nil
func setupLogic() error
// run your logic container
return nil
func checkResult() error
// db.Query(), your code with checking result
return nil
【讨论】:
以上是关于与测试方法分开在 docker 应用程序中运行 docker 映像的主要内容,如果未能解决你的问题,请参考以下文章