💬

ent+dockertest

2023/08/17に公開

ent+dockertest

https://github.com/ory/dockertest

https://pkg.go.dev/testing

PostgreSQLで、dockertestを実装します。

dockertestは、テストを実行するとコンテナが立ち上がり、終了すると削除されます。

Sample

server_test.go
package dockertestpsql_test

import (
	"database/sql"
	"example/ent"
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	_ "github.com/lib/pq"
	"github.com/ory/dockertest/v3"
	"github.com/ory/dockertest/v3/docker"
	log "github.com/sirupsen/logrus"
)

var (
	db     *sql.DB
	client *ent.Client
)

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)
	}

	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.RunWithOptions(&dockertest.RunOptions{
		Repository: "postgres",
		Tag:        "15",
		Env: []string{
			"POSTGRES_PASSWORD=postgres",
			"POSTGRES_USER=postgres",
			"POSTGRES_DB=postgres",
			"listen_addresses = '*'",
		},
	}, func(config *docker.HostConfig) {
		// set AutoRemove to true so that stopped container goes away by itself
		config.AutoRemove = true
		config.RestartPolicy = docker.RestartPolicy{Name: "no"}
	})
	if err != nil {
		log.Fatalf("Could not start resource: %s", err)
	}

	hostAndPort := resource.GetHostPort("5432/tcp")
	databaseUrl := fmt.Sprintf("postgres://postgres:postgres@%s/postgres?sslmode=disable", hostAndPort)
	parts := strings.Split(hostAndPort, ":")
	host := parts[0]
	port := parts[1]
	entUrl := fmt.Sprintf("host=%s port=%s user=postgres dbname=postgres password=postgres sslmode=disable", host, port)

	log.Println("Connecting to database on url: ", databaseUrl)

	resource.Expire(120) // Tell docker to hard kill the container in 120 seconds

	// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
	pool.MaxWait = 120 * time.Second
	if err = pool.Retry(func() error {
		db, err = sql.Open("postgres", databaseUrl)
		if err != nil {
			return err
		}
		return db.Ping()
	}); err != nil {
		log.Fatalf("Could not connect to docker: %s", err)
	}

	client, err = ent.Open("postgres", entUrl)
	if err != nil {
		log.Fatalf("failed opening connection to postgres: %v", err)
	}
	defer client.Close()

	//Run tests
	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 TestRealbob(t *testing.T) {
	// all tests
}

TestMainは、テストの実行のセットアップをします。

CloudBuildで自動テスト

https://github.com/ory/dockertest/issues/145

いくつか修正します。

server_test.go
package dockertestpsql_test

import (
	"database/sql"
	"example/ent"
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	_ "github.com/lib/pq"
	"github.com/ory/dockertest/v3"
	"github.com/ory/dockertest/v3/docker"
	log "github.com/sirupsen/logrus"
)

var (
	db     *sql.DB
	client *ent.Client
)

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)
	}

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

+	list, _ := pool.NetworksByName("cloudbuild")

	// pulls an image, creates a container based on it and runs it
	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
		Repository: "postgres",
		Tag:        "15",
		Env: []string{
			"POSTGRES_PASSWORD=postgres",
			"POSTGRES_USER=postgres",
			"POSTGRES_DB=postgres",
			"listen_addresses = '*'",
		},
+		Networks: []*dockertest.Network{&list[0]},
	}, func(config *docker.HostConfig) {
		// set AutoRemove to true so that stopped container goes away by itself
		config.AutoRemove = true
		config.RestartPolicy = docker.RestartPolicy{Name: "no"}
	})
	if err != nil {
		log.Fatalf("Could not start resource: %s", err)
	}

-	hostAndPort := resource.GetHostPort("5432/tcp")
+	hostAndPort := getPort(resource, "5432")
	databaseUrl := fmt.Sprintf("postgres://postgres:postgres@%s/postgres?sslmode=disable", hostAndPort)
	parts := strings.Split(hostAndPort, ":")
	host := parts[0]
	port := parts[1]
	entUrl := fmt.Sprintf("host=%s port=%s user=postgres dbname=postgres password=postgres sslmode=disable", host, port)

	log.Println("Connecting to database on url: ", databaseUrl)

	resource.Expire(120) // Tell docker to hard kill the container in 120 seconds

	// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
	pool.MaxWait = 120 * time.Second
	if err = pool.Retry(func() error {
		db, err = sql.Open("postgres", databaseUrl)
		if err != nil {
			return err
		}
		return db.Ping()
	}); err != nil {
		log.Fatalf("Could not connect to docker: %s", err)
	}

	client, err = ent.Open("postgres", entUrl)
	if err != nil {
		log.Fatalf("failed opening connection to postgres: %v", err)
	}
	defer client.Close()

	//Run tests
	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 getPort(r *dockertest.Resource, port string) string {
+		n := r.Container.NetworkSettings.Networks["cloudbuild"]
+		return net.JoinHostPort(n.IPAddress, port)
+ }

func TestRealbob(t *testing.T) {
	// all tests
}

cloudbuild.yamlに以下のコマンドを追加します。

https://cloud.google.com/build/docs/building/build-go?hl=ja

cloudbuild.yaml
+ steps:
+  # Run tests and save to file
+  - name: golang:1.20
+    entrypoint: /bin/bash
+    args:
+      - -c
+      - |
+        go install github.com/jstemmer/go-junit-report/v2@latest
+        2>&1 go test -timeout 1m -v ./path/to/testdirectry... | /go/bin/go-junit-report -set-exit-code -iocopy -out ${SHORT_SHA}_test_log.xml
+    # 並列実行
+    waitFor: ['-']
+ options:
+  # 高性能CPU
+  machineType: 'E2_HIGHCPU_32'

Discussion