【问题标题】:Run docker images in a docker application separately from test method与测试方法分开在 docker 应用程序中运行 docker 映像
【发布时间】: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 在所有情况下对我来说都包含不必要的开销

标签: docker go


【解决方案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】:

    对于您的方法:

    1. 启动 MySQL 映像。
    2. 将数据上传到数据库。
    3. 启动逻辑映像。
    4. 检查数据库以获取结果。

    你可以:

    使用 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
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-22
      • 1970-01-01
      • 1970-01-01
      • 2022-01-18
      • 2021-12-10
      • 2017-01-29
      • 1970-01-01
      相关资源
      最近更新 更多