RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

Dockerコンテナを活用したテストツール【Testcontainers】

はじめに

こんにちは! エンジニア2年目のTKDSです!
前回はDaggerを紹介しました。
今回もコンテナ技術を活用して、テストを容易にするツールについて紹介します。
今回取り上げるのは、統合テストやエンドツーエンドテストのためにDockerコンテナを利用するライブラリ、Testcontainersです。

Testcontainersとは

Testcontainersはさまざまなプログラミング言語Java、Go、Python、Node.jsなど)向けに提供されており、Daggerと同様にテスト用のコンテナを簡単に作成することができます。
前回の記事で紹介したユースケース2と同様のことが実現できるため、テスト内にコンテナの起動コードを直接書くことが可能です。
Testcontainersを使用することでモックを使用せずに外部サービスに依存するテストを書くことが可能になります。

Testcontainersのメリット

次にTestcontainersのメリットについて紹介します。

  1. モックを使わずにテストをかける
    前述のようにコンテナでDBなどの外部サービスを用意することでモックに依存しないテストが書けます。
    コンテナで用意できる対象は、DBに限らず、MQやNoSQL、Key-value storeなど多岐に渡ります。
    事前に用意されているモジュールやコンテナイメージを直接指定してコンテナを作ることができます。
    2つ目の方法については後ほど詳しく説明します。

  2. 事前に用意されたモジュールがある
    前項目で述べたようにTestcontainers moduleが用意されているため、面倒な準備なしに様々なミドルウェアが使用可能です。

  3. ローカル環境と CI 環境の両方で一貫した環境の用意が可能
    コンテナで環境が用意できるため、ローカルでは実行できるのにCIではテストが落ちる、またはその逆など環境依存のテストの不安定さを取り除けます。

参考
- https://testcontainers.com/getting-started/
- https://testcontainers.com/guides/
- https://testcontainers.com/

ハンズオン

では、実際にTestcontainersを使ってみます。
今回はGoを使います。

環境設定

筆者の環境

shun@shun-ThinkPad-P14s-Gen-4:~$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.4 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
shun@shun-ThinkPad-P14s-Gen-4:~$ docker version
Client: Docker Engine - Community
 Version:           26.1.3
 API version:       1.45
 Go version:        go1.21.10
 Git commit:        b72abbb
 Built:             Thu May 16 08:33:29 2024
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          26.1.3
  API version:      1.45 (minimum version 1.24)
  Go version:       go1.21.10
  Git commit:       8e96db1
  Built:            Thu May 16 08:33:29 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.32
  GitCommit:        8b3b7ca2e5ce38e8f31a34f35b2b68ceb8470d89
 runc:
  Version:          1.1.12
  GitCommit:        v1.1.12-0-g51d5e94
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

事前にDockerのインストールは済ませておいてください。

goプロジェクトの作成

mkdir go-testcontainers
cd go-testcontainers
go mod init go-testcontainers

必要なパッケージのインストール

go get github.com/testcontainers/testcontainers-go
go get github.com/testcontainers/testcontainers-go/modules/postgres
go get github.com/jackc/pgx/v4

テストコードの作成

コード全体はgithubにおいてあります。
Testcontainersに関わる部分だけ説明します。
今回は事前に用意されたモジュールを使わずにDBを用意します。

コンテナリクエストの設定

この部分では、コンテナを起動するためのリクエストを設定しています。

   req := testcontainers.ContainerRequest{
        Image:        "postgres:latest",
        ExposedPorts: []string{"5432/tcp"},
        Env: map[string]string{
            "POSTGRES_DB":       "testdb",
            "POSTGRES_USER":     "testuser",
            "POSTGRES_PASSWORD": "testpassword",
        },
        WaitingFor: wait.ForListeningPort("5432/tcp"),
    }
  • Image: 使用するDockerイメージを指定します。ここではpostgres:latestを指定しており、最新のPostgreSQLイメージを使用します。
  • ExposedPorts: コンテナの公開ポートを指定します。PostgreSQLはデフォルトで5432ポートを使用するため、5432/tcpを指定しています。
  • Env: コンテナ内の環境変数を設定します。ここではデータベース名、ユーザー名、パスワードを設定しています。
  • WaitingFor: コンテナが準備完了になるまで待機する条件を指定します。ここでは、5432ポートがリスニング状態になるのを待ちます。

参考
- https://golang.testcontainers.org/quickstart/
- https://github.com/testcontainers/testcontainers-go/blob/v0.31.0/container.go#L123

コンテナの起動

ここでは、設定したリクエストに基づいてコンテナを起動しています。

   postgresContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
        ContainerRequest: req,
        Started:          true,
    })
    if err != nil {
        t.Fatal(err)
    }
    defer postgresContainer.Terminate(ctx)
  • testcontainers.GenericContainer: 汎用的なコンテナを起動するための関数です。
  • ctx: コンテキストを渡します。これは操作のキャンセルやタイムアウトを管理するために使用されます。
  • ContainerRequest: 先ほど設定したコンテナリクエストを渡します。
  • Started: true: コンテナがすぐに起動するように指定します。

コンテナのホストとポートの取得

ここでは、起動したコンテナのホストとマッピングされたポートを取得しています。

   host, err := postgresContainer.Host(ctx)
    if err != nil {
        t.Fatal(err)
    }

    port, err := postgresContainer.MappedPort(ctx, "5432")
    if err != nil {
        t.Fatal(err)
    }
  • postgresContainer.Host(ctx): コンテナのホスト名を取得します。通常はlocalhostになります。
  • postgresContainer.MappedPort(ctx, "5432"): コンテナ内の5432ポートがホストのどのポートにマッピングされているかを取得します。

結果の確認

実際にテストを実行し、結果を確認してみましょう。

go mod tidy
go test -v ./…

無事テストが実行されたことが確認できました!
画像のように、通常のgo testで実行することができ、非常に簡単にDB依存のテストが実行できます。

まとめ

今回はTestcontainersについて紹介しました。
今まではモジュールを使った方法しか知らなかったため、今回のハンズオンで触れてみた内容は新たな学びになりました。
自分でやりたいことを設定できるのであれば、Daggerの代わりに使ってみるのもありかもしれません。
特に、コンテナを用意するだけでCI/CDパイプラインを書かない・Goを使用しない等であれば有力な選択肢になりそうです。
ここまで読んでいただきありがとうございました。

Copyright © RAKUS Co., Ltd. All rights reserved.