Documentation

§データベースを使用したテスト

ScalaTestspecs2 を使って、データベースを含むアプリケーション全体を起動してデータベースアクセスコードをテストするような機能テストを書くことも可能ではありますが、アプリケーションのたった一部分をテストするために多くのコンポーネントを立ち上げるという複雑性により、アプリケーション全体を立ち上げるのは通常好ましくありません。

Play はデータベースアクセスコードをテストする補助となる多くのユーティリティを提供しており、他の部分から独立してデータベースのみをテストできるようにしています。このようなユーティリティは ScalaTest や specs2 から容易に使用でき、データベースのテストを重くて遅い機能テストからより軽量で高速なものとすることができます。

§データベースを使う

データベースに接続するには、最低限、データベースドライバ名とデータベースの URL が必要で、 Databases コンパニオンオブジェクトを使用します。例えば、 MySQL に接続するには以下のようにします。

import play.api.db.Databases

val database = Databases(
  driver = "com.mysql.jdbc.Driver",
  url = "jdbc:mysql://localhost/test"
)

これによって、 default という名前の localhost で実行される MySQL の test データベースへのコネクションプールが生成されます。データベースの名前は、例えば evolutions などの他の機能ではデータベースに関連づけられたリソースをロードするなど、Play の内部でのみ使用されるものです。

独自の name や 、 username や password、Play がサポートする様々なコネクションプール設定アイテムなどの設定プロパティなど、データベースについて他にも設定したいときは、カスタム name パラメータやカスタム config パラメータを与えることができます。

import play.api.db.Databases

val database = Databases(
  driver = "com.mysql.jdbc.Driver",
  url = "jdbc:mysql://localhost/test",
  name = "mydatabase",
  config = Map(
    "user" -> "test",
    "password" -> "secret"
  )
)

一般的に、データベースを支えるコネクションプールは、開いているコネクションや、ことによると実行中かもしれないスレッドを保持しているので、データベースの使用後には閉じる必要があります。これは shutdown メソッドで行うことができます。

database.shutdown()

手動でデータベースを生成したり閉じたりするのは、テストフレームワークを使っていて、それぞれのテストやスイートごとに立ち上げと終了を行っている場合に便利です。そうでない場合は、Play にコネクションプールを管理させるのをお勧めします。

§Play にデータベースを管理させる

Play は、Play によって管理されたデータベースコネクションプールを使って実行するためのコードのブロックを与えられるようにする、 withDatabase ヘルパーも提供しています。Play はコードブロックの実行後にコネクションプールが適切に閉じられるのを保証します。

import play.api.db.Databases

Databases.withDatabase(
  driver = "com.mysql.jdbc.Driver",
  url = "jdbc:mysql://localhost/test"
) { database =>
  val connection = database.getConnection()
  // ...
}

Database.apply ファクトリメソッドと同様に、 withDatabase に対しても必要に応じて独自の nameconfig マップを渡すことができます。

特に、それぞれのテストで withDatabase を直接使うとボイラープレートコードがかなりの量になるので、独自のヘルパーを定義し、使用されるボイラープレートを除去するのをおすすめします。

import play.api.db.{Database, Databases}

def withMyDatabase[T](block: Database => T) = {
  Databases.withDatabase(
    driver = "com.mysql.jdbc.Driver",
    url = "jdbc:mysql://localhost/test",
    name = "mydatabase",
    config = Map(
      "user" -> "test",
      "password" -> "secret"
    )
  )(block)
}

すると、それぞれのテストで最小限のボイラープレートで簡単に使用できます。

withMyDatabase { database =>
  val connection = database.getConnection()
  // ...
}

Tip: これを使用してテストデータベースの設定を外部化できます (環境変数やシステムプロパティをどのデータベースを使用しどのように接続するかの設定に使用します)。これにより、開発者に対しては好みの方法でそれぞれの環境を構築できるようにする最大の柔軟性を、そして同様に CI システムに対しても 開発環境とは異なりうる特定の環境を提供することができます。

§インメモリデータベースを使用する

テストを実行するためにデータベースのようなインフラストラクチャを求めたくない人もいます。Play はこれらの目的で H2 インメモリデータベースを作成するための単純なヘルパーを提供しています。

import play.api.db.Databases

val database = Databases.inMemory()

インメモリデータベースは、カスタム name やカスタム URL 引数、カスタムコネクションプール設定を提供するとこで設定可能です。以下では、H2 に MySQL をエミュレートさせるために MODE 引数を与えつつ、すべてのステートメントをログに出力するためにコネクションプールを設定しています。

import play.api.db.Databases

val database = Databases.inMemory(
  name = "mydatabase",
  urlOptions = Map(
    "MODE" -> "MYSQL"
  ),
  config = Map(
    "logStatements" -> true
  )
)

一般的なデータベースファクトリと同様、インメモリデータベースのコネクションプールを確実に閉じます。

database.shutdown()

テストフレームワークの before/after 機能を使用していない場合に、Playにインメモリデータベースのライフサイクルを管理させたいことがあるかもしれません。その場合は直接 withInMemory を使用してください。

import play.api.db.Databases

Databases.withInMemory() { database =>
  val connection = database.getConnection()
  
  // ...
}

withDatabase と同様、ボイラープレートコードを削減することをお勧めしますが、そうする場合は、 withInMemory 呼び出しをラップする独自のメソッドを作成します。

import play.api.db.{Database, Databases}

def withMyDatabase[T](block: Database => T) = {
  Databases.withInMemory(
    name = "mydatabase",
    urlOptions = Map(
      "MODE" -> "MYSQL"
    ),
    config = Map(
      "logStatements" -> true
    )
  )(block)
}

§evolutions の適用

テストを実行する際には、よくデータベーススキーマをデータベースのために管理したいことがあります。evolutions をすでに使用している場合は、テストでも開発環境や本番環境と同じ evolutions を使用することにはしばしば意味があります。テストのためだけに独自の evolutions を作りたくなるかもしれません。Play は、Play アプリケーション全体を起動することなく evolutions を適用、管理する便利なヘルパーをいくつか提供しています。

evolutions を適用するには、 Evolutions コンパニオンオブジェクトから applyEvolutions を使用できます。

import play.api.db.evolutions._

Evolutions.applyEvolutions(database)

これは evolutions/<databasename> ディレクトリ内のクラスパスから evolutions をロードし、適用します。

テストが実行されたら、データベースを元の状態にリセットしたいこともあるでしょう。すべてのデータベーステーブルを削除するように evolutions の down スクリプトを実装してあれば、 単純にcleanupEvolutions メソッドを呼ぶことでこれを実現できます。

Evolutions.cleanupEvolutions(database)

§カスタム evolutions

テストでカスタム evolutions を実行したい場合があるかもしれません。カスタム evolutions はカスタム EvolutionsReader を使用することで行うことができます。最も簡単なのは SimpleEvolutionsReader です。これは evolutions のリーダーであり、データベース名を Evolution スクリプトのシーケンスに対して事前に設定したマップをとり、 SimpleEvolutionsReader コンパニオンオブジェクトに定義された便利なメソッドを使用して生成されます。例:

import play.api.db.evolutions._

Evolutions.applyEvolutions(database, SimpleEvolutionsReader.forDefault(
  Evolution(
    1,
    "create table test (id bigint not null, name varchar(255));",
    "drop table test;"
  )
))

カスタム evolutions のクリーンアップは、 cleanupEvolutions メソッドを使用することで、通常の evolutions のクリーンアップと同じ方法で行うことができます。

Evolutions.cleanupEvolutions(database)

しかし、ここではカスタム evolutions リーダーを渡す必要がないことに注意してください。というのも、(データベースを解体するのに使われる down スクリプトを含む) evolutions の状態はデータベースに保持されるからです。

カスタム evolutions スクリプトをコードに埋め込みたいという特殊な事例があります。その場合は、それらをテストリソースディレクトリに配置し、 ClassLoaderEvolutionsReader を使用したカスタムパスに配置します。例:

import play.api.db.evolutions._

Evolutions.applyEvolutions(database, ClassLoaderEvolutionsReader.forPrefix("testdatabase/"))

これは、testdatabase/evolutions/<databasename>/<n>.sql から、開発環境や本番環境で行われるのと同様の構造・フォーマットで evolutions をロードします。

§Play に evolutions の管理をさせる

applyEvolutionscleanupEvolutions メソッドは、テストフレームワークを使用してテストの前後に evolutions を実行するのを管理している場合に便利です。Play は withEvolutions という、もっと簡易なアプローチが望ましい場合に便利なメソッドも用意しています。

import play.api.db.evolutions._

Evolutions.withEvolutions(database) {
  val connection = database.getConnection()

  // ...
}

もちろん、 withEvolutions はボイラープレートを減らすために withDatabasewithInMemory と自然に結合できるので、データベースを初期化し evolutions を実行することができる関数を定義することができます。

import play.api.db.{Database, Databases}
import play.api.db.evolutions._

def withMyDatabase[T](block: Database => T) = {

  Databases.withInMemory(
    urlOptions = Map(
      "MODE" -> "MYSQL"
    ),
    config = Map(
      "logStatements" -> true
    )
  ) { database =>

    Evolutions.withEvolutions(database, SimpleEvolutionsReader.forDefault(
      Evolution(
        1,
        "create table test (id bigint not null, name varchar(255));",
        "drop table test;"
      )
    )) {

      block(database)

    }
  }
}

テスト用のカスタムデータベース管理メソッドを定義したので、これらを以下の様な簡単な形で使用できます。

withMyDatabase { database =>
  val connection = database.getConnection()
  connection.prepareStatement("insert into test values (10, 'testing')").execute()

  connection.prepareStatement("select * from test where id = 10")
    .executeQuery().next() must_== true
}

Next: web サービスクライアントのテスト


このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。