testcontainer-goを試す

はじめに

JavaのライブラリーにTestcontainersと呼ばれるものがありますが、そいつのGo版ライブラリがあると言うのを見かけたので試してみたい思います。

Testcontainersとは

TestcontainersはJavaのライブラリーでDocker ContainerをJunitのライフサイクルに合わせてコンテナの作成、削除等の操作できたり、SeleniumやPostgres DBなどのインテグレーションテストで利用するようなコンテナの利用をサポートしていたりします。

TestContainers-Goとは

Testcontainer-GoはTestContainersのGoの実装でです。 Docker Engine SDKをベースに作られています。
このブログを書いている現在はVersionがv0.5.1で本家ほどのテストライブラリに対するサポートなどは無いようです。

使ってみる

環境

$ uname -srvmpio
Linux 5.3.0-59-generic #53~18.04.1-Ubuntu SMP Thu Jun 4 14:58:26 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
LSB Version:    core-9.20170808ubuntu1-noarch:security-9.20170808ubuntu1-noarch
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic

$ go version
go version go1.14.4 linux/amd64

$ docker -v
Docker version 19.03.11, build 42e35e61f3

Testcontainers-Goのインストール

まずは、モジュールプロジェクトを作成して、Testcontainers-Goをインストールします。

$ go mod init hello-testcontainers

$ go get github.com/testcontainers/testcontainers-go

コンテナを作成してみる

Testcontainers-Goを使ってNginxのコンテナを作成して、リクエストを送ってみます。

package hello_testcontainers__test

import (
    "context"
    "fmt"
    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/wait"
    "io/ioutil"
    "net/http"
    "testing"
)


func TestHelloTestContainer(t *testing.T) {
    cxt := context.Background()

    request := testcontainers.ContainerRequest{
        Image: "nginx",
        ExposedPorts: []string{"80/tcp"},
        WaitingFor: wait.ForHTTP("/"),
    }

    container, err := testcontainers.GenericContainer(cxt, testcontainers.GenericContainerRequest{
        ContainerRequest: request,
        Started:          true,
    })
    checkErr(t, err)

    defer container.Terminate(cxt)

    host, err := container.Host(cxt)
    checkErr(t, err)

    port, err := container.MappedPort(cxt, "80")
    checkErr(t, err)

    resp, err := http.Get(fmt.Sprintf("http://%s:%s", host, port.Port()))
    checkErr(t, err)

    body, err := ioutil.ReadAll(resp.Body)
    checkErr(t, err)

    if resp.StatusCode != http.StatusOK {
        t.Errorf("get wrong status code")
    }
    fmt.Println(string(body))
}

func checkErr(t *testing.T,err error)  {
    if err != nil {
        t.Error(err)
    }
}

testcontainers.ContainerRequestはコンテナ実行に必要なパラメータを持つための構造体です。上記のコードでは、作成するイメージの名前と、Exposeするポート、そして、NginxコンテナのReadinessをチェックするためのストラテジーを設定しています。

testcontainers.ContainerRequestを使用してGenericContainerを作成しています。

   container, err := testcontainers.GenericContainer(cxt, testcontainers.GenericContainerRequest{
        ContainerRequest: request,
        Started:          true,
    })
    checkErr(t, err)

このコートが実行される際に実際にコンテナが作成します。 試しにこのコードが実行された後でコードの実行をポーズしてみて実行中のイメージを見てみると、Nginxのコンテナが作成され実行されているのがわかります。

$ docker ps
CONTAINER ID        IMAGE                               COMMAND                  CREATED             STATUS              PORTS                     NAMES
5531bb0974ad        nginx                               "/docker-entrypoint.…"   8 seconds ago       Up 8 seconds        0.0.0.0:32785->80/tcp     vigorous_dubinsky
e94eebca8f70        quay.io/testcontainers/ryuk:0.2.3   "/app"                   9 seconds ago       Up 8 seconds        0.0.0.0:32784->8080/tcp   ecstatic_wilbur

nginxとは別にquay.io/testcontainers/ryuk:0.2.3のコンテナも作成されています。このコンテナはmoby-ryukuと呼ばれる。 Testcontainersが用意しているコンテナの削除を補助するためのコンテナです。

container.Terminate(cxt)はコンテナを停止するための関数でdeferで実行することによって、コードの実行が終わった後に呼び出してコンテナを停止しています。

   host, err := container.Host(cxt)
    checkErr(t, err)

    port, err := container.MappedPort(cxt, "80")
    checkErr(t, err)

上記の箇所では、起動されるコンテナのホストと、コンテナ内の80に対してポートフォワードされるポートを取得することができます。

コードの最後の部分では、 hostportを使ってコンテナ内のnginxに対してHttpのゲットリクエストを送り、HTTPステータスをアサーションし、レスポンスボディを出力しています。

上記のコードを実行すると、テストが成功し、以下の出力を得ることができます。

GOROOT=/usr/local/go #gosetup
GOPATH=/home/yuya-hirooka/go #gosetup
/usr/local/go/bin/go test -c -o /tmp/___TestHelloTestContainer_in_hello_testcontainers -gcflags "all=-N -l" hello-testcontainers #gosetup
/usr/local/go/bin/go tool test2json -t /home/yuya-hirooka/.local/share/JetBrains/IntelliJIdea2020.1/intellij-go/lib/dlv/linux/dlv --listen=localhost:34941 --headless=true --api-version=2 --check-go-version=false --only-same-user=false exec /tmp/___TestHelloTestContainer_in_hello_testcontainers -- -test.v -test.run ^TestHelloTestContainer$ #gosetup
API server listening at: 127.0.0.1:34941
=== RUN   TestHelloTestContainer
2020/06/13 14:55:04 Starting container id: e94eebca8f70 image: quay.io/testcontainers/ryuk:0.2.3
2020/06/13 14:55:04 Waiting for container id e94eebca8f70 image: quay.io/testcontainers/ryuk:0.2.3
2020/06/13 14:55:05 Container is ready id: e94eebca8f70 image: quay.io/testcontainers/ryuk:0.2.3
2020/06/13 14:55:05 Starting container id: 5531bb0974ad image: nginx
2020/06/13 14:55:05 Waiting for container id 5531bb0974ad image: nginx
2020/06/13 14:55:05 Container is ready id: 5531bb0974ad image: nginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

--- PASS: TestHelloTestContainer (144.01s)
PASS

Debugger finished with exit code 0

また、テスト実行後はコンテナは削除されています。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

感想

使ってみましたが、本家ほどのテストライブラリーなどに対するサポートは無いようです。 一旦コンテナの起動、実行、削除に関する一通りのことができるくらいのものが用意されている理解です。

参考資料