Scalaのテストケース内でDockerコンテナを使う

GitBucketではもともとデータベースを使ったテストに組み込みMySQLやPostgreSQLの機能を提供するライブラリを使っていたのですが、これらのライブラリのメンテナンスが怪しくなかなか新しいバージョンに対応したテストを行うことができないという問題があり、Dockerを使えないかなと思っていたところ、以下のようなライブラリを教えてもらいました。

github.com

プログラム中からDockerコンテナを制御するためのライブラリで、Scala版の他にもJava版やGo版など様々な言語向けのライブラリが提供されています(Scala版はJava版のラッパーのようです)。

デフォルトでよく使うコンテナ向けの実装が提供されているのが特徴で、たとえばMySQLを使う場合、まずは以下の依存関係をbuild.sbtに追加します。

libraryDependencies ++= Seq(
  "com.dimafeng" %% "testcontainers-scala" % "0.22.0" % "test",
  "org.testcontainers" % "mysql" % "1.10.3" % "test"
)

以下のような感じでコンテナを使ったテストを簡単に行うことができます。JDBC URLやユーザ名、パスワードをコンテナから取得できるのが地味に便利です。

import com.dimafeng.testcontainers.{ForAllTestContainer, MySQLContainer}

class MysqlSpec extends FlatSpec with ForAllTestContainer {

  override val container = MySQLContainer()

  it should "do something" in {
    Class.forName(container.driverClassName)
    val connection = DriverManager.getConnection(
      container.jdbcUrl, container.username, container.password)
    ...
  }
}

複数のコンテナを使いたい場合はMultipleContainersを使います。

val mySqlContainer = MySQLContainer()
val postgresContainer = PostgreSQLContainer()

override val container = MultipleContainers(
  mySqlContainer, postgresContainer)

また、DockerComposeContainerでdocker-compose.ymlを使ってコンテナを起動することもできるようです。

override val container = DockerComposeContainer(
  new File("src/test/resources/docker-compose.yml"))

ちなみにデフォルトで用意されていないコンテナを使いたい場合はGenericContainerあたりを継承して自分で作成します。JDBC経由でアクセスするデータベースであればJdbcDatabaseContainerを継承するのが簡単です。

コンテナの起動をForAllTestContainerで制御するのではなく、コード中で直接制御することもできます。

val container = new MySQLContainer()
container.starting()
...
container.finished()

GitBucketでは特定のテストケースで複数の種類・バージョンのデータベースでテストを行う必要があったので、以下のような感じでテスト内で必要に応じて対象バージョンのコンテナを起動するような使い方をしています。

Seq("11", "10").foreach { tag =>
  test(s"Migration PostgreSQL $tag") {
    val container = PostgreSQLContainer(s"postgres:$tag")
    container.starting()
    try {
      // ここでテスト
    } finally {
      container.finished()
    }
  }
}

簡単に使える上で拡張性もあってなかなか便利なライブラリですね。

なお、同種のライブラリとして以下のようなものもあるようです。こちらでも同じようなことができそうですが、プログラム中からコンテナ単体での制御を行うことはあまり考えられていなそうな印象です。

github.com