前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang集成测试:dockertest testcontainers-go

golang集成测试:dockertest testcontainers-go

作者头像
golangLeetcode
发布2023-03-01 16:21:25
6550
发布2023-03-01 16:21:25
举报
文章被收录于专栏:golang算法架构leetcode技术php

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

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

代码语言:javascript
复制
% docker version
 Version:           20.10.12

dockertest

代码语言:javascript
复制
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

代码语言:javascript
复制
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()
}

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

代码语言:javascript
复制
https://golang.testcontainers.org/examples/redis/
https://github.com/ory/dockertest/tree/v3/examples
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-02-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器镜像服务
容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档