testcontainers-goを使ってテスト時にカジュアルにコンテナを起動する

testcontainers-go

というツールがあるので使ってみる。
testcontainersはGoに限らずいろんな言語で実装が存在する。

自分は過去にScalaのライブラリをお触りしたことがある。

testcontainersと言っているものの、用途は別にtestに限るなんてことはない。いい感じにコンテナを立ち上げて落とせるライブラリなのだ。

使ってみた

READMEを参考に徐にこんな関数を定義してみる。

package test_utils

import (
	"context"
	"fmt"
	"github.com/docker/go-connections/nat"
	"github.com/go-faster/errors"
	_ "github.com/lib/pq"
	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/wait"
	"log"
)

func PSQLContainer(ctx context.Context) (testcontainers.Container, error) {
	psqlPort, err := nat.NewPort("tcp", "5432")
	if err != nil {
		return nil, errors.Wrap(err, "failed to NewPort")
	}

	req := testcontainers.ContainerRequest{
		Image:        "postgres:latest",
		ExposedPorts: []string{fmt.Sprintf("%s/%s", psqlPort.Port(), psqlPort.Proto())},
		WaitingFor: wait.ForSQL(psqlPort, "postgres", func(port nat.Port) string {
			return fmt.Sprintf("postgres://localhost:%s?user=postgres&password=password&sslmode=disable", port.Port())
		}),
		Env: map[str
ing]string{
			"POSTGRES_PASSWORD": "password",
		},
	}

	psqlContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
		ContainerRequest: req,
		Started:          true,
	})
	if err != nil {
		return nil, errors.Wrap(err, "failed to GenericContainer creation")
	}

	return psqlContainer, nil
}

利用してみる。

func Test_PSQL_Query(t *testing.T) {
	t.Run("run select 1", func(t *testing.T) {
		ctx := context.Background()
		rdbContainer, err := test_utils.PSQLContainer(ctx)
		if err != nil {
			t.Fatalf("failed to PSQLContainer creation: %v", err)
		}
		psqlEndpoint, err := rdbContainer.Endpoint(context.Background(), "postgres")
		if err != nil {
			t.Fatalf("failed to get postgres endpoint: %v", err)
		}
		source := fmt.Sprintf("%s/sample?user=postgres&password=password&sslmode=disable", psqlEndpoint)

		db, err := sql.Open("postgres", source)
		if err != nil {
			t.Fatalf("failed to sql.Open: %v", err)
		}

		result, err := db.Query("select 1")
		if err != nil {
			log.Fatalf("main sql.Open error err: %v", err)
		}

		defer result.Close()
		for result.Next() {
			var number int
			if err := result.Scan(&number); err != nil {
				t.Fatalf("failed to Scan Result: %v", err)
			}
			t.Logf("number=%d", number)
		}

		if err := result.Err(); err != nil {
			t.Fatalf("result.Err()=$%v", err)
		}

		defer db.Close()
		if err != nil {
			t.Fatalf("failed to start RDB %v", err)
		}
		defer rdbContainer.Terminate(ctx)
	})
}

Container型のリソースをクリーンアップするのに defer container.Terminate(ctx) としているのはGoらしい。
Scalaの場合はscalatest用の拡張がテストケースやテストスイート終了時に勝手に実行してくれていた。

つまずくこと

Scalaのライブラリを利用したことがあったので基本的につまずくことなくサッと試せた。
あえてつまずいたところをあげるとするなら、
rdbContainer.Endpoint(context.Background(), "postgres")
のようにEndpointメソッドを使っているところだ。
このメソッド、Contaner interfaceに定義されているが、第二引数で何を指定すればいいのか初見でまるでわからなかった。

type Container interface {
  // 略
  Endpoint(context.Context, string) (string, error) // get proto://ip:port string for the first exposed port
  // 略
}

Goのインターフェースのメソッド、引数の名前を省略できる故に…
コメントを見て推理したところ、第二引数はコメント上のprotoにあたる部分を宣言するということがわかった。
ということで、
rdbContainer.Endpoint(context.Background(), "postgres")
の返り値は
postgres://localhost:49354
的な感じになる。

手が止まったところはそんなところだ。

所感

testcontainersは最高に便利だったのでGoの実装があって最高!!という感じ。

JavaやScalaと違い、postgres用コンテナ、localstack用コンテナなど用途に特化したヘルパ付きの実装の提供はまだ内容だった。
ないところで、便利さがガクッとさがるほどでもないので自分的にはガッカリ度はない。

とにかくtestcontainerがあるとAWSリソースやデータベースとやり取りする箇所のテストがカンタンに書けるので推しのライブラリだ。

この記事が気に入ったらサポートをしてみませんか?