Go用のTestcontainersでのモジュールの作成

多くの場合、プロジェクトは特定のテクノロジに非常に反復的な方法で依存しており、そのテクノロジと対話するためにコードを 1 つのプロジェクトから別のプロジェクトに移動する状況に陥る可能性があります。 「このコードをライブラリにパッケージ化して、どこでも利用してみてはどうだろうか」

そのとき、モジュール性、デカップリング、凝集性など、特定のソフトウェアエンジニアリングのベストプラクティスを思い出すのです。

このブログ記事では、 Testcontainers for Goの新しいモジュールを作成し、プロジェクトで使用するテクノロジーと、コードをテストするためのテクノロジーを表す方法について説明します。

go用のtestcontainersでモジュールを作成するバナー

モジュールを最初から作成する

Testcontainers for Go モジュールは、特定のテクノロジの API を公開する Go モジュールにすぎません。 Go モジュールをゼロから簡単に作成できるように、Testcontainers for Go には、関心のあるモジュールのコードのスキャフォールディングを生成するコマンドライン ツールが用意されているため、すぐに開始できます。

このツールは以下を生成します。

  • このテクノロジーのGoモジュールには、次のものが含まれます。
    • go.mod Go用のTestcontainersの現在のバージョンを含む go.sum ファイル。
    • モジュールにちなんで名付けられた小文字の Go パッケージ。
    • コンテナを作成するための Go ファイルで、イメージ フラグの値が Docker イメージとして使用される専用の構造体を使用します。
    • コンテナの簡単なテストを実行するためのGoテストファイルで、上記の構造体を消費します。
    • 一貫した方法でテストを実行するための Makefile。
  • docs/modulesディレクトリ内のマークダウンファイルには、コンテナの作成と簡単なテストの両方のスニペットが含まれています。デフォルトでは、この生成されたファイルには、以下を含むモジュールのすべてのドキュメントが含まれます。
    • モジュールが追加された Testcontainers for Go のバージョン。
    • モジュールの簡単な紹介です。
    • プロジェクトの依存関係にモジュールを追加するためのセクション。
    • Go モジュールの examples_test.go ファイルからコンテナを作成するためのスニペットを含む、使用例のセクション。
    • コンテナを作成するためのエントリポイント関数や、コンテナを作成するためのオプションなど、モジュール参照のセクション。
    • コンテナメソッドのセクションです。
  • ドキュメント サイト内のモジュールの新しい Nav エントリで、プロジェクトのルート ディレクトリにある mkdocs.yml ファイルに追加します。
  • .github/workflows内の GitHub ワークフロー ファイル ディレクトリに移動して、テクノロジのテストを実行します。
  • プロジェクトのワークスペースに新しいモデルを含めるための VSCode ワークスペース ファイル内のエントリ。

コード生成ツール

コード生成ツールは、Goの依存関係がTestcontainers for Goに分散されるのを避けるために、Goモジュールとして modulegen ディレクトリに存在するGoプログラムであり、その唯一の目的は、Goテンプレートを使用してGoモジュールのスキャフォールディングを作成することです。

コード生成ツールのコマンドライン フラグを表 1に示します。

種類必須形容
–名前はいモジュールの名前。必要に応じてキャメルケースを使用してください。 英数字のみを使用できます (先頭の文字は文字である必要があります)。
–画像 はいモジュールが使用する Docker イメージの完全修飾名 ( docker.io/org/project:tag)
–タイトルいいえ大文字と小文字の混在をサポートする名前のバリアント (MongoDB など)。 英数字のみを使用できます (先頭の文字は文字である必要があります)。
表 1: コード生成ツールのコマンド ライン フラグ。

モジュール名またはタイトルに英数字が含まれていない場合、プログラムは生成を終了します。 また、モジュールがすでに存在する場合は、既存のファイルを更新せずに終了します。

modulegenディレクトリから、次のコマンドを実行します。

go run . new module --name ${NAME_OF_YOUR_MODULE} --image "${REGISTRY}/${MODULE}:${TAG}" --title ${TITLE_OF_YOUR_MODULE}

モジュールへの型とメソッドの追加

モジュールに型とメソッドを追加する際に従うべき一連の手順を提案します。

  1. モジュールにパブリック Container タイプが存在することを確認します。 このタイプは、 testcontainers.Container タイプを埋め込むためにコンポジションを使用し、そこからすべての方法を促進する必要があります。
  2. RunContainer関数が存在し、パブリックであることを確認してください。この関数はモジュールへのエントリポイントであり、イメージ、デフォルトの公開ポート、待機戦略など、 testcontainers.GenericContainerRequest 構造体の初期値を定義します。 その結果、関数はコンテナリクエストをデフォルト値で初期化する必要があります。
  3. モジュールのコンテナオプションを定義するには、 testcontainers.ContainerCustomizer インターフェースを使用します。このインターフェースには Customize(req *GenericContainerRequest) error つの方法があります。

手記: オプションのベスト プラクティスは、With プレフィックスを使用して関数を定義し、変更された testcontainers.GenericContainerRequest 型を返す関数を返すことであると考えています。 そのため、ライブラリにはContainerCustomizerインターフェイスを実装するtestcontainers.CustomizeRequestOption型がすでに用意されているため、この型を使用して独自のカスタマイザー関数を作成することをお勧めします。

  1. 同時に、モジュール用に独自のコンテナカスタマイザーを作成する必要がある場合があります。 testcontainers.ContainerCustomizerインターフェースを実装していることを確認します。独自のカスタマイザー関数を定義することは、コンテナの ContainerRequest に存在しない特定の状態を転送する必要がある場合や、場合によっては中間の Config 構造体を使用する必要がある場合に便利です。 以下のスニペットで MyCustomizerWithMy を見てみましょう。
  2. オプションは、Go コンテキストの後に可変個引数として RunContainer 関数に渡され、for ループを使用して最初の testcontainers.GenericContainerRequest 構造体を定義した直後に処理されます。
  3. 必要に応じて、 Container 型をレシーバーとして使用して、実行中のコンテナから情報を抽出するパブリックメソッドを定義します。 たとえば、データベースにアクセスするための接続文字列が役立つ方法として考えられます。 コンテナメソッドを追加する際には、ユーザーにとって何が役立つかを考えてください。
  4. 公開APIをGoコメントで文書化します。
  5. モジュールの新しい API を説明するためにドキュメントを拡張します。 コード生成ツールでは、Container options サブセクションと Container methods サブセクションを含む親 Module reference セクションがすでに作成されています。各サブセクション内で、各オプションとメソッドのネストされたサブセクションをそれぞれ定義してください。

次のスニペットは、コードがどのように見えるかの例を示しています。

type ModuleContainer struct {
   testcontainers.Container
   cfg *Config
}
// Config type represents an intermediate struct for transferring state from the options to the container
type Config struct {
   data string
}
// RunContainer is the entrypoint to the module
func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*Container, error) {
   cfg := Config{}
   req := testcontainers.ContainerRequest{
       Image: "my-image",
       //...
   }
   genericContainerReq := testcontainers.GenericContainerRequest{
       ContainerRequest: req,
       Started:          true,
   }
   //...
   for _, opt := range opts {
       if err := opt.Customize(&genericContainerReq); err != nil {
       	return nil, err
       }
       // If you need to transfer some state from the options to the container, you can do it here
       if myCustomizer, ok := opt.(MyCustomizer); ok {
           config.data = customizer.data
       }
   }
   //...
   container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
   //...
   moduleContainer := &Container{Container: container}
   // use config.data here to initialize the state in the running container
   //...
   return moduleContainer, nil
}
// MyCustomizer type represents a container customizer for transferring state from the options to the container
type MyCustomizer struct {
   data string
}
// Customize method implementation
func (c MyCustomizer) Customize(req *testcontainers.GenericContainerRequest) testcontainers.ContainerRequest {
   req.ExposedPorts = append(req.ExposedPorts, "1234/tcp")
   return req.ContainerRequest
}
// WithMy function option to use the customizer
func WithMy(data string) testcontainers.ContainerCustomizer {
   return MyCustomizer{data: data}
}
// WithSomeState function option leveraging the functional option
func WithSomeState(value string) testcontainers.CustomizeRequestOption {
   return func(req *testcontainers.GenericContainerRequest) {
       req.Env["MY_ENV_VAR"] = value
   }
}
// ConnectionString returns the connection to the module container
func (c *Container) ConnectionString(ctx context.Context) (string, error) {...}

ContainerRequest オプション

特定のモジュールのコンテナの作成を簡略化するために、Testcontainers for Go には、モジュールのコンテナリクエストをカスタマイズするための一連の testcontainers.CustomizeRequestOption 関数が用意されています。 これにより、サービスのコンテナリクエストを完全にカスタマイズできます。

これらのオプションは次のとおりです。

  • testcontainers.WithImage: コンテナリクエストのイメージを設定する関数。
  • testcontainers.WithImageSubstitutors: コンテナイメージに独自の置換を設定する関数。
  • testcontainers.WithEnv: コンテナリクエストの環境変数を設定する関数。
  • testcontainers.WithHostPortAccess: ホストで既に実行されているポートにコンテナがアクセスできるようにする関数。
  • testcontainers.WithLogConsumers: コンテナリクエストのログコンシューマを設定する関数。
  • testcontainers.WithLogger: コンテナリクエストのロガーを設定する関数。
  • testcontainers.WithWaitStrategy: コンテナリクエストの待機ストラテジーを設定する関数で、渡されたすべての待機ストラテジーをコンテナリクエストに追加し、期限が 60 秒のtestcontainers.MultiStrategyを使用します。詳細については 、待機戦略 を参照してください。
  • testcontainers.WithWaitStrategyAndDeadline: 渡された期限を持つ testcontainers.MultiStrategy を使用して、渡されたすべての待機ストラテジーをコンテナリクエストに追加し、コンテナリクエストの待機ストラテジーを設定する関数。 詳細については 、待機戦略 を参照してください。
  • testcontainers.WithStartupCommand: コンテナの起動時にコマンドの実行を設定する関数。
  • testcontainers.WithAfterReadyCommand: コンテナの準備ができた (待機戦略が満たされている) 直後にコマンドの実行を設定する関数。
  • testcontainers.WithNetwork: コンテナリクエストのネットワークとネットワークエイリアスを設定する関数。
  • testcontainers.WithNewNetwork: コンテナリクエストの使い捨てネットワークのネットワークエイリアスを設定する関数。
  • testcontainers.WithConfigModifier: コンテナリクエストの設定Dockerタイプを設定する関数。 詳細については、「 詳細設定 」を参照してください。
  • testcontainers.WithEndpointSettingsModifier: コンテナリクエストのエンドポイント設定の Docker タイプを設定する関数。 詳細については、「 詳細設定 」を参照してください。
  • testcontainers.WithHostConfigModifier: コンテナリクエストのホスト設定 Docker タイプを設定する関数。 詳細については、「 詳細設定 」を参照してください。
  • testcontainers.CustomizeRequest: デフォルトの testcontainers.GenericContainerRequest をユーザーが提供するものとマージする関数。 コンテナリクエストを完全にカスタマイズする場合に推奨されます。

それらのいずれかを使用して、 RunContainer 関数を呼び出すときにコンテナをカスタマイズできます。 LocalStack モジュールの例を次に示します。

container, err := localstack.RunContainer(ctx,
testcontainers.WithImage("localstack/localstack:2.0.0"),
testcontainers.WithEnv(map[string]string{"SERVICES": "s3,sqs"}),
)

結論

この記事では、Testcontainers for Go の Go モジュールを作成し、カスタム型、メソッド、カスタマイザーを追加する方法を共有しました。 最後に、コード生成ツールを使用して新しいモジュールのスキャフォールディングを作成する方法を発見しました。

モジュールについてもっと知りたいですか? 次のリンクを確認してください。

さらに詳しく