暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

golang集成测试:dockertest testcontainers-go

        在做集成测试的时候,每次测试前,如果通过docker重启一个干净的容器是不是免去了数据清理的苦恼。https://github.com/testcontainers/testcontainers-go和https://github.com/ory/dockertest可以解决我们的苦恼,它们很相似都是调用docker的api实现镜像的拉取和容器的启动关闭。然后我们可以基于容器做对应的集成测试。

        由于每次拉取镜像和启动docker代价比较大,比较耗时,我们一般在单测的入口TestMain方法里做初始化,也就是一个模块进行一次容器初始化。由于单测case之间没有数据的清理,因此我们每个单测结束后都需要注意清理和还原数据。整体来说dockertest testcontainers-go 原理和使用方法比较类似。下面我们体验一下用法,首先我们需要启动docker

    % docker version
    Version: 20.10.12

    dockertest

      package dockertest_test


      import (
      "database/sql"
      "fmt"
      "log"
      "os"
      "testing"


      _ "github.com/go-sql-driver/mysql"
      "github.com/ory/dockertest/v3"
      )


      var db *sql.DB


      func TestMain(m *testing.M) {
      // uses a sensible default on windows (tcp/http) and linux/osx (socket)
      pool, err := dockertest.NewPool("")
      if err != nil {
      log.Fatalf("Could not construct pool: %s", err)
      }


      // uses pool to try to connect to Docker
      err = pool.Client.Ping()
      if err != nil {
      log.Fatalf("Could not connect to Docker: %s", err)
      }


      // 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 {
      log.Fatalf("Could not start resource: %s", err)
      }


      // 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 {
      log.Fatalf("Could not connect to database: %s", err)
      }


      code := m.Run()


      // You can't defer this because os.Exit doesn't care for defer
      if err := pool.Purge(resource); err != nil {
      log.Fatalf("Could not purge resource: %s", err)
      }


      os.Exit(code)
      }


      func TestSomething(t *testing.T) {
      var CREATE_TABLE = "CREATE TABLE student(" +
      "sid INT(10) NOT NULL AUTO_INCREMENT," +
      "sname VARCHAR(64) NULL DEFAULT NULL," +
      "age INT(10) DEFAULT NULL,PRIMARY KEY (sid))" +
      "ENGINE=InnoDB DEFAULT CHARSET=utf8;"
      var INSERT_DATA = `INSERT INTO student(sid,sname,age) VALUES(?,?,?);`
      var QUERY_DATA = `SELECT * FROM student;`


      db.Query("create database test;")
      db.Query("use test ;")
      _, err := db.Exec(CREATE_TABLE)
      fmt.Println("err")
      db.Exec(INSERT_DATA, 1, "唐僧", 30)


      // 查询数据
      rows, err := db.Query(QUERY_DATA)
      if err != nil {
      fmt.Println(err)
      }
      for rows.Next() {
      var name string
      var id int
      var age int
      if err := rows.Scan(&id, &name, &age); err != nil {
      fmt.Println(err)
      }
      fmt.Printf("%s is %d\n", name, age)
      }
      }


      testcontainers-go

        package exp1


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


        "github.com/go-redis/redis/v8"
        "github.com/google/uuid"
        "github.com/stretchr/testify/require"
        "github.com/testcontainers/testcontainers-go"
        "github.com/testcontainers/testcontainers-go/wait"
        )


        type redisContainer struct {
        testcontainers.Container
        URI string
        }


        func setupRedis(ctx context.Context) (*redisContainer, error) {
        req := testcontainers.ContainerRequest{
        Image: "redis:6",
        ExposedPorts: []string{"6379/tcp"},
        WaitingFor: wait.ForLog("* Ready to accept connections"),
        }
        container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
        ContainerRequest: req,
        Started: true,
        })
        if err != nil {
        return nil, err
        }


        mappedPort, err := container.MappedPort(ctx, "6379")
        if err != nil {
        return nil, err
        }


        hostIP, err := container.Host(ctx)
        if err != nil {
        return nil, err
        }


        uri := fmt.Sprintf("redis://%s:%s", hostIP, mappedPort.Port())


        return &redisContainer{Container: container, URI: uri}, nil
        }


        func TestIntegrationSetGet(t *testing.T) {
        if testing.Short() {
        t.Skip("Skipping integration test")
        }


        ctx := context.Background()


        redisContainer, err := setupRedis(ctx)
        if err != nil {
        t.Fatal(err)
        }
        t.Cleanup(func() {
        if err := redisContainer.Terminate(ctx); err != nil {
        t.Fatalf("failed to terminate container: %s", err)
        }
        })


        // You will likely want to wrap your Redis package of choice in an
        // interface to aid in unit testing and limit lock-in throughtout your
        // codebase but that's out of scope for this example
        options, err := redis.ParseURL(redisContainer.URI)
        if err != nil {
        t.Fatal(err)
        }
        client := redis.NewClient(options)
        defer flushRedis(ctx, *client)


        t.Log("pinging redis")
        pong, err := client.Ping(ctx).Result()
        require.NoError(t, err)


        t.Log("received response from redis")


        if pong != "PONG" {
        t.Fatalf("received unexpected response from redis: %s", pong)
        }


        // Set data
        key := fmt.Sprintf("{user.%s}.favoritefood", uuid.NewString())
        value := "Cabbage Biscuits"
        ttl, _ := time.ParseDuration("2h")
        err = client.Set(ctx, key, value, ttl).Err()
        if err != nil {
        t.Fatal(err)
        }


        // Get data
        savedValue, err := client.Get(ctx, key).Result()
        if err != nil {
        t.Fatal(err)
        }
        if savedValue != value {
        t.Fatalf("Expected value %s. Got %s.", savedValue, value)
        }
        fmt.Println(key, savedValue)
        }


        func flushRedis(ctx context.Context, client redis.Client) error {
        return client.FlushAll(ctx).Err()
        }

        两个包中的例子都列举了常用的中间件的用法,可以参考下

          https://golang.testcontainers.org/examples/redis/
          https://github.com/ory/dockertest/tree/v3/examples

          文章转载自golang算法架构leetcode技术php,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论