#头条创做挑战赛#

在做集成测试的时候,每次测试前,若是通过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_testimport ( "database/sql" "fmt" "log" "os" "testing" _ "github.com/go-sql-driver/mysql" "github.com/ory/dockertest/v3")var db *sql.DBfunc 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 exp1import ( "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