Scalatraのテストに関する公式ドキュメントがあまり充実していないので、書いてみました。
ScalatraアプリケーションのE2Eテスト
WebアプリケーションのE2E(End to End)テストと言えばSeleniumやHeadless Cromeのようなヘッドレスブラウザを使った方法が定番ですが、Scalaのツール類とはまったく異なる環境を用意する必要が有り、手軽に実行するには少々難が有ります。
そこでScalatraにはscalatra-test
という、ScalatraアプリケーションのE2Eテストをサポートするライブラリが用意されていて、Scala用のメジャーなテスティングフレームワークであるScalaTestやspecs2の記法を使ってテストを記述できます。
以下のコードは、GETメソッドを使ってroot path("/")にアクセスするとHTTPのステータスコード200が返却されることをテストします。
test("GET / on MyScalatraServlet should return status 200"){
get("/"){
status should equal (200)
}
}
ScalaTestに馴染みにある方であれば、上記のテストコードがScalaTestのFunSuiteというテストスタイルを使って記述されていることが分かることでしょう。
ScalaTest - Getting started with FunSuite
テストの実行も通常のScalaのユニットテストと同様にsbt
から行います。
$ sbt test
このようにscalatra-test
は、既存のScalaのツールやテスティングフレームワークを使って素早くE2Eテストを書けるところが最大の特徴です。
scalatra-testの'実装'と、'使いどころ'
scalatra-test
はApache HttpComponentsのHTTPクライアント機能をバックエンドに使って実装されているため、ChromeやSafariなどのブラウザの挙動を再現できるわけではありません(JavaScriptの実行機能などはありません)。
あくまでHTTPリクエストを送信し、受信したHTTPレスポンスの内容をテストするだけです(PerlのPlack::Testや、RubyのRack::Testに近いイメージ)。しかしながらCookieを始めとするサーバサイドアプリケーションの動作確認に必要なクライアント機能は一通りサポートされているので、ログインや画面遷移を含む基本的なアプリケーションの動作検証や、APIサーバのテストなどには充分な機能を備えていると言えるでしょう。
'サーバサイドアプリケーションの挙動'を確認するテストはscalatra-test
、'フロントエンドも含めたアプリケーション全体の挙動'を確認するテストはSeleniumやヘッドレスブラウザベースのテスティングツールを使う、といった方法が良いでしょう。
# サンプルコード
では、実際にscalatra-test
を使ったE2Eテストの例を見ていきましょう。ここではScalaTestを使います。
プロジェクトの作成
scalatraのプロジェクトテンプレートからプロジェクトを作ります。
$ sbt new scalatra/scalatra.g8
生成されたbuild.sbt
にはScalaTestを使ったテストに対応するためにscalatra-scalatest
というパッケージが記述されています。
"org.scalatra" %% "scalatra-scalatest" % "2.6.2" % "test",
scalatra-scalatest
がscalatra-test
に依存していて、その依存関係は自動的にsbtが解決してくれるので、明示的なscalatra-test
パッケージの指定は不要です。
実際のテストは、src/test/scala/com/example/app/MyScalatraServletTests.scala
に記述されています。
package com.example.app
import org.scalatra.test.scalatest._
class MyScalatraServletTests extends ScalatraFunSuite {
addServlet(classOf[MyScalatraServlet], "/*")
test("GET / on MyScalatraServlet should return status 200"){
get("/"){
status should equal (200)
}
}
}
ScalaTestとspecs2ではテスト記法は異なりますが、テストを実行するまでの基本的な流れは同一です。順に解説していきます。
-
パッケージのインポート
import org.scalatra.test.scalatest._
scalatra-scalatest
を有効にします。Specs2を使う場合は、org.scalatra.test.specs2._
を使います。 -
scalatestまたはspecs2用のtraitを継承したテストクラスを用意する
class MyScalatraServletTests extends ScalatraFunSuite
コード例ではscalatestが複数用意しているテストスタイルのうち、
FunSuite
を使ったスタイルを選択するためにScalatraFunSuite
を継承しています。FlatSpec
やFunSpec
など、scalatestが用意する別のテストスタイルも使えますし、Specs2であればAcceptance specification
とUnit specification
の両方の記法が使えます。 -
テスト対象のservletを指定する
addServlet(classOf[MyScalatraServlet], "/*")
ScalatraBootstrap
の中でServletを指定するときと同様に、パス名と共にservletのクラスを指定します。new MyScalatraServlet
のようにオブジェクトを直接渡してもOKです。servletだけでなく、servletFilterも指定できます。
-
テストを用意する
test("GET / on MyScalatraServlet should return status 200")
テストを用意します。ScalaTestではメソッド定義の形式を取っていないので少し分かりづらいですが、
ScalatraFunSuite
はtest
メソッドを使ってテスト名とテストコードを「登録」しておく仕組みになっています。class定義の中ですぐに実行されるわけではありません。 -
HTTPメソッドでリクエスト条件(パス、パラメータ等)を指定する
get("/") { }
ここが一番のポイントとなる箇所です。Scalatraのルーティング定義と同じような記法ですが、ここではリクエストのメソッドとパスを指定しています。ホスト名は指定せずに、パス名だけを指定します。
パス名に自前でパラメータを連結しても正しく動作しますが、分かりづらいので、
get(uri = "/", params = Seq("header1", "param1"))
のように、uriとは別引数でパラメータを渡すこともできます。ここも色々な呼び出し方がサポートされていますので、後ほど詳しく解説します。
-
レスポンスの内容を検証し、テストの成否を判定する
{ status should equal (200) }
status
,header
,body
といった主要な要素が引き渡されるので、コードブロックの中でテスティングフレームワークが提供するマッチャーを使って、レスポンスの正当性を判定します。ここでは
status
が200,つまり正常にレスポンスが返ってきたことを判定しています。コードブロックの返り値がそのままテスト結果になるので、マッチャーによる判定はコードブロックの一番最後に書きます。
また、ScalaTestの場合、
Matcher trait
が予め有効になっているので、should 〜
という書き方が追加のimport無しにできるようになっています(通常のScalaTestでは明示的にimportする必要が有ります)。
上記のテストをsbt
から、testタスク
で実行すると、下記のような表示がされて、テストの成否が分かります。
[info] MyScalatraServletTests:
[info] - GET / on MyScalatraServlet should return status 200
[info] Run completed in 1 second, 284 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 2 s, completed Dec 30, 2017 10:51:59 PM
更に詳しい使い方は後日、続きのエントリで解説します。
Enjoy Testing!!