在做集成测试的时候,每次测试前,如果通过docker重启一个干净的容器是不是免去了数据清理的苦恼。https://github.com/testcontainers/testcontainers-go和https://github.com/ory/dockertest可以解决我们的苦恼,它们很相似都是调用docker的api实现镜像的拉取和容器的启动关闭。然后我们可以基于容器做对应的集成测试。
由于每次拉取镜像和启动docker代价比较大,比较耗时,我们一般在单测的入口TestMain方法里做初始化,也就是一个模块进行一次容器初始化。由于单测case之间没有数据的清理,因此我们每个单测结束后都需要注意清理和还原数据。整体来说dockertest testcontainers-go 原理和使用方法比较类似。下面我们体验一下用法,首先我们需要启动docker
% docker versionVersion: 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 Dockererr = 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 itresource, 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 yetif err := pool.Retry(func() error {var err errordb, 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 deferif 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 stringvar id intvar age intif 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.ContainerURI 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 exampleoptions, 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 datakey := 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 datasavedValue, 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进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




