§アプリケーションのテスト
アプリケーションのテストを書くことは、複雑な工程になりがちです。
PlayFrameworkでは、標準でテストフレームワークをサポートしており、ヘルパーやアプリケーションスタブを用意しているため、簡単にテストを組むことができます。
§概要
テストのソースファイルは test
フォルダに配置します。
2つのテンプレートとして使えるサンプルファイルが test
フォルダに置いてあるので、参照してみてください。
テストを実行する際、 Play の場合コンソールから実行できます。
test
を実行すると全てのテストが走ります。test-only
を実行すると、その後に続くクラス名のテストクラスのみが実行されます。 例)test-only my.namespace.MySpec
- 失敗したテストだけ走らせたい場合は、
test-quick
を実行します。 - 継続的にテストを走らせたい場合は、実行するテストコマンドの前にチルダをつけます。 例)
~test-quick
SBT でテストを実行する際は、 testing SBT に詳細が記載されています。
§specs2 を使う
Play の標準的なテストでは、 specs2 を利用します。
spec2 のテストでは、異なったコードパスのテストを実行する例を、まとめて記述します。
Specification
trait を継承した仕様で should/in のフォーマットを使って記述します。:
import org.specs2.mutable._
class HelloWorldSpec extends Specification {
"The 'Hello world' string" should {
"contain 11 characters" in {
"Hello world" must have size(11)
}
"start with 'Hello'" in {
"Hello world" must startWith("Hello")
}
"end with 'world'" in {
"Hello world" must endWith("world")
}
}
}
仕様は IntelliJ IDEA ( Scala pluginを利用 ) か Eclipse ( Scala IDEを利用 ) で実行することができます。 詳しくは IDE page を見てください。
メモ:presentation compiler のバグで、 Eclipse でコーディングする場合、特定のフォーマットで書く必要があります。
- ディレクトリパスとパッケージは同じでなければいけません。
- 仕様は、
@RunWith(classOf[JUnitRunner])
でアノテーションされなければいけません。
Eclipse では以下のように書きます。
package models // このファイルは、「models」というディレクトリに配置されている必要があります。
import org.specs2.mutable._
import org.specs2.runner._
import org.junit.runner._
@RunWith(classOf[JUnitRunner])
class ApplicationSpec extends Specification {
...
}
§Matchers
テスト例を使った際、テスト例の結果を返さなければいけません。大体、 must
という宣言がよく使われます。
"Hello world" must endWith("world")
must
に続く表現は、 matchers
と呼ばれます。
matchers はテスト例の結果を成功か失敗かで返します。結果を返さない場合、テスト例はコンパイルされません。
最も使いやすい matchers は、match results です。
等しいかどうかや Option や Either の結果を判定したり、例外が投げられたかどうかをチェックします。
他にも XML や JSON を比較するテストの optional matchers などもあります。
§Mockito
モックは外部の依存関係から独立したユニットテストを行う際に用います。
例えば、外部の DataService
クラスに依存している場合、 DataService
オブジェクトのインスタンスを作らなくても、モックを使ってデータを提供できます。
Mockito は、標準の mocking library として、 spec2 に組み込まれています。
Mockito は以下のように使います。
import org.specs2.mock._
ビルドに library dependency を追加します。
Mockito を使えば、次のように参照をモックに向けさせられます。
"MyService#isDailyData" should {
"return true if the data is from today" in {
val mockDataService = mock[DataService]
mockDataService.findData returns Data(retrievalDate = new java.util.Date())
val myService = new MyService() {
override def dataService = mockDataService
}
val actual = myService.isDailyData
actual must equalTo(true)
}
}
モックは、パブリックなメソッドに対してのテストとしてとても便利です。
モックオブジェクトとプライベートメソッドの組み合わせでも可能ですが、大変です。
§モデルのユニットテスト
Play はモデルを使う際に、特定のデータベースアクセス層を必要としません。
しかしながら、アプリケーションが Anorm や Slick を使っていた場合、内部的にモデルからデータベースへの参照が頻繁に行われるでしょう。
import anorm._
import anorm.SqlParser._
case class User(id: String, name: String, email: String) {
def roles = DB.withConnection { implicit connection =>
...
}
}
モデルのユニットテストを行うにあたり、 roles
メソッドを使うことでモック化することが出来ます。
一般的なアプローチでは、モデルをデータベースから隔離し、ロジックに集中させ、リポジトリ層を利用して抽象的にデータベースアクセスを行います。
case class Role(name:String)
case class User(id: String, name: String, email:String)
trait UserRepository {
def roles(user:User) : Set[Role]
}
class AnormUserRepository extends UserRepository {
import anorm._
import anorm.SqlParser._
def roles(user:User) : Set[Role] = {
...
}
}
そして、サービスを経由してアクセスします。
class UserService(userRepository : UserRepository) {
def isAdmin(user:User) : Boolean = {
userRepository.roles(user).contains(Role("ADMIN"))
}
}
この方法で、 UserRepository
を参照しサービス層へ渡すことで、 isAdmin
メソッドをモックでテストすることができます。
object UserServiceSpec extends Specification with Mockito {
"UserService#isAdmin" should {
"be true when the role is admin" in {
val userRepository = mock[UserRepository]
userRepository.roles(any[User]) returns Set(Role("ADMIN"))
val userService = new UserService(userRepository)
val actual = userService.isAdmin(User("11", "Steve", "[email protected]"))
actual must beTrue
}
}
}
§コントローラーのユニットテスト
Play ではコントローラーはオブジェクトとして定義されるため、ユニットテストでトリッキーなことができます。
Play では、 getControllerInstance
を使って、 dependency injection でユニットテストを簡単に出来ます。
コントローラーをテストするトリッキーな他の方法は、 明示的に型付けられた自己参照 上の trait をコントローラーに使います。
trait ExampleController {
this: Controller =>
def index() = Action {
Ok("ok")
}
}
object ExampleController extends Controller with ExampleController
そして、 trait のテストを追加します。
object ExampleControllerSpec extends PlaySpecification with Results {
class TestController() extends Controller with ExampleController
"Example Page#index" should {
"should be valid" in {
val controller = new TestController()
val result: Future[SimpleResult] = controller.index().apply(FakeRequest())
val bodyText: String = contentAsString(result)
bodyText must be equalTo "ok"
}
}
}
Next: Writing functional tests