39
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

playframeworkと僕とUnitTest

Posted at

この記事は Play framework 2.x Scala Advent Calendar 2013 の20日目の記事です。

6日ぶり2回目…

playframeworkを使う上で、こんな感じでテスト書いてます的な

突然お昼に振られたので、書いてますww

前回同様これからPlayframeworkを触りだすお隣チーム向けでしょうか?

Controllerのテストをする

  • 目的はWithApplication/WithServerから出来る限り逃げる

playのControllerをUnit Testするサンプルです

テスト系のドキュメントは2.1系から急に充実した感ありますので、2.0系以来その辺見てない〜という方は見るだけで幸せ(?)になれるかもしれません

http://www.playframework.com/documentation/2.2.x/ScalaTest ← ここの一番下のUnit Testing Controllers ですね
http://www.playframework.com/documentation/2.2.x/ScalaFunctionalTest ← 実際にはこっちの下の方の Testing a controllerかな?

上記のように書くことでControllerのメソッドをテストする際にWithApplication/WithServerからだいたい逃げられます ← すごく重要
なお、分離しても play.api.Play.currentが必要〜みたいな状況にしてしまうとダメです
実行時にRuntimeException: There is no started applicationとか言われるときは、だいたいそれです
なんとか分離しましょう

ということで下に一応サンプル

なんかこう色々と突っ込みどころありますけど気にしないで

trait SampleController {
  this: Controller =>

  def hoge(x:Int) = Action.async { implicit request =>
    Future{
      val y = Form( "multi" -> optional(number)).bindFromRequest.fold(
        error => 0,
        optM => optM.getOrElse(2)
      )
      x * y
    }.map(res => Ok(Json.obj("res" -> res)))
  }
}
import org.specs2._

import play.api.test._
import play.api.test.Helpers._
import play.api.libs.json._
import play.api.mvc.Controller

class SampleControllerSpec extends Specification {
  def is = s2"""

### path parameterに2を渡す

optionパラメータなしなら ${E1ExpectVal.toString} を返す $e1
optionパラメータ10なら ${E2ExpectVal.toString} を返す $e2
optionパラメータに文字列なら ${E3ExpectVal.toString} を返す $e3

"""

  val baseExpectResult:JsValue = {
    val s =
      s"""
        |{
        |  "res" : 4
        |}
      """.stripMargin
    Json.parse(s)
  }

  val expectResultTransform = { res:Int => __.json.update( (__ \ 'res).json.put(JsNumber(res))) }

  val E1ExpectVal = 4
  val E2ExpectVal = 20
  val E3ExpectVal = 0

  // 以下全て成功する前提にしている
  val e1Expect = baseExpectResult.transform(expectResultTransform(E1ExpectVal)).get
  val e2Expect = baseExpectResult.transform(expectResultTransform(E2ExpectVal)).get
  val e3Expect = baseExpectResult.transform(expectResultTransform(E3ExpectVal)).get

  val target = {
    val c = new SampleController with Controller
    c.hoge(2)
  }

  def e1 = {
    val res = target(FakeRequest())
    (status(res) === OK).updateMessage(s => s"s : statusがおかしい").orThrow
    contentAsJson(res) === e1Expect
  }

  def e2 = {
    val res = target(FakeRequest(GET, "/?multi=10"))
    (status(res) === OK).updateMessage(s => s"s : statusがおかしい").orThrow
    contentAsJson(res) === e2Expect
  }

  def e3 = {
    val res = target(FakeRequest(GET, "/?multi=hoge"))
    (status(res) === OK).updateMessage(s => s"s : statusがおかしい").orThrow
    contentAsJson(res) === e3Expect
  }

}

長いけど全部載せました

もちろん普段はもっとちゃんと書いてますw

単にControllerのメソッドをUnitテストしたいのなら

実はtraitにする必要性は全くありません

Action#applyをみると、引数にRequestを受け取っていることがわかります

なので、

def hoge = Action { request => Ok}

をとりあえず動かしたかったら、

hoge(FakeRequest())

で動きます、いやった〜

POSTリクエストでJsonをdataとして渡したいとか、色々とあると思いますが、
FakeRequestをちら見すればとりあえずだいたい解決します

じゃあなんでtraitにするの?

object SampleController extends Controller でいいじゃんという話ですけど、
例えば永続化層へのつなぎ込みとかが必要になった場合にtraitのほうが都合が良いです
→ 最初に上げた、play.api.Play.currentから逃げる方法にもなります

その他、所詮traitなんで、上手に分離すると色々と使い回せる?


// routeからはこっちを参照
object Application extends Controller with SampleController {
  val hogeModel = HogeModel
}

// ロジックは分離
trait SampleController {
  this : Controller =>

  def hogeModel : HogeModel

  def hoge() = Aciton{request =>
    hogeModel.find(...

   // 略
}

class SampleControllerSpec ... {

  // test時はmockに差し替える
  val targetController = new SampleController extends Controller {
    def hogeModel = MockHogeModel
  }

  // 以下なんかテスト
}

cake pattern 使うなら使うでも良いと思いますが、この辺は割りとテキトーにやっています

個人的には上記の様な例だと、testはprojectを分けたくなります
分けることによってMock/StubをTestプロジェクトで使いまわしやすいと思うからです

階層構造的にはこのような感じでしょうか?

.
├── build.sbt
└── modules
    ├── web
    │   ├── app
    │   │   ├── controllers
    │   │   │   └── SampleController.scala
    │   │   └── models
    │   │       └── HogeModel.scala
    │   └── conf
    └── webtest
        ├── app
        │   └── models
        │       └── MockHogeModel.scala
        └── test
            ├── controllers
            │   └── SampleControllerSpec.scala
            └── models
                └── HogeModelSpec.scala

webtest.dependson(web) 的な

デメリットは言わずもがな、最初の構築がめんどい(なれればそうでもないんですけど…)

あとフツーにMockitoとか使えばいいじゃんとかあると思いますが、
その辺はきっと好み?

個人的には上記構造だと webtest モジュールにMock的なものだったりFixture的なものだったりするものはゴリゴリ切り出せるので、ガリガリ切り出しています

PlayでUnitTest とは関係なくなってきている気がする…

FakeApplicationとかはいつ使っているのか?

あくまで個人的にはですが、基本的にはrouteまで確認するような結合テスト的な、シナリオテスト的なところで使えばいいんじゃないかな…みたいな。

まあ面倒なので出来る限り使わないようにしています(おい)

まとめ

そもそもフツーのsbtのプロジェクトとして切り出し、playはroutingとcontrollerだけ使うようにするのがいい気がする

という状況が本当は一番テストしやすいと思いますw

明日は… まだ誰もいない?

気が向いたらこの辺でやっていることを別エントリーで書きます(たぶん

39
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?