Specs2を試す(6)

Given/When/Then

Given/When/Then というスタイルでspecを書くこともできる。

// コード1
import org.specs2._
import org.specs2.specification._
import org.specs2.execute._

class GivenWhenThenSpec extends Specification { def is =

    "A given-when-then example for the addition"                 ^
      "Given the following number: ${1}"                         ^ number1 ^
      "And a second number: ${2}"                                ^ number2 ^
      "Then I should get: ${3}"                                  ^ result ^
                                                                 end

    object number1 extends Given[Int] {
      def extract(text: String): Int = extract1(text).toInt
    }
    case class Addition(n1: Int, n2: Int) {
      def add: Int = n1 + n2
    }
    object number2 extends When[Int, Addition] {
      def extract(number1: Int, text: String) = Addition(number1, extract1(text).toInt)
    }
    object result extends Then[Addition] {
      def extract(addition: Addition, text: String): Result = addition.add must_== extract1(text).toInt
    }
}

number1がGiven。型パラメータのIntはextractメソッドが返す型。


Text部分で${}で括られた値はextractNというメソッドで取得することができる。
extractNはextract10まであって、extract2〜extract10についてははタプルを返してくる。コード2参照。


number2はWhen。ここではnumber1で取得した結果の型であるIntと、その結果と2つ目の数(${2}の部分、つまり2)から作りだしたAddition型が型引数となっている。
ここではextractメソッドの定義は
extract(Int, String): Addition
となる。


最後のresultがThenステップでここではAddition型とTextを引数にとってResultを返している。

// コード2
import org.specs2._
import org.specs2.specification._
import org.specs2.execute._

class GivenWhenThenSpec extends Specification { def is =

    "A given-when-then example for the addition"                 ^
      "Given the following number: ${1}, ${2}, ${3}"             ^ number1 ^
      "And a second number: ${4}"                                ^ number2 ^
      "Then I should get: ${10}"                                  ^ result ^
                                                                 end

    object number1 extends Given[Int] {
      def extract(text: String): Int = {
        extract3(text) match {
          case (x, y, z) => x.toInt + y.toInt + z.toInt
        }
      }
    }
    case class Addition(n1: Int, n2: Int) {
      def add: Int = n1 + n2
    }
    object number2 extends When[Int, Addition] {
      def extract(number1: Int, text: String) = Addition(number1, extract1(text).toInt)
    }
    object result extends Then[Addition] {
      def extract(addition: Addition, text: String): Result = addition.add must_== extract1(text).toInt
    }
}

Multiple Steps

Given/When/Thenは例えばG/W/W/T/TのようにWhenやThenが複数になってもよい。ただし

  • 最初はGiven[T]
  • Given[T]の次はWhen[T, S]かThen[T]
  • When[T, S]の次はWhen[S, U]かThen[S]
  • Then[S]の次はThen[S]


具体的には

  • Given[T] / When[T, S] / Then[S]
  • Given[T] / When[T, S] / Then[S] / Then[S]
  • Given[T] / Then[T] / Then[T]
  • Given[T] / When[T, S] / When[S, U] / Then[U]

Several G/W/T blocks

2つ以上のGiven/When/Thenを書きたいときは一度endで終わらせる。

    "A given-when-then example for the addition"                 ^
      "Given the following number: ${1}"                         ^ number1 ^
      "And a second number: ${2}"                                ^ number2 ^
      "Then I should get: ${3}"                                  ^ addition ^
                                                                 end^ // ☆
    "A given-when-then example for the multiplication"           ^
      "Given the following number: ${1}"                         ^ number1 ^
      "And a second number: ${2}"                                ^ number2 ^
      "Then I should get: ${2}"                                  ^ multiplication ^
                                                                 end

User regexps

${} を使わずに自分で正規表現を書いて使うこともできる。
メリットはTextの部分がそのままReportに出てくれること、デメリットは同じようなものを2回書くことになるのでめんどくさいということ。

// コード3
import org.specs2._
import org.specs2.specification._
import org.specs2.execute._

class GivenWhenThenSpec extends Specification { def is =

    "A given-when-then example for the addition"                 ^
      "Given the following number: 1"                            ^ number1 ^
      "And a second number: ${2}"                                ^ number2 ^
      "Then I should get: ${3}"                                  ^ result ^
                                                                 end

    object number1 extends Given[Int]("Given the following number: (.*)") { // 抽出用正規表現
      def extract(text: String): Int = extract1(text).toInt
    }

    case class Addition(n1: Int, n2: Int) {
      def add: Int = n1 + n2
    }
    object number2 extends When[Int, Addition] {
      def extract(number1: Int, text: String) = Addition(number1, extract1(text).toInt)
    }
    object result extends Then[Addition] {
      def extract(addition: Addition, text: String): Result = addition.add must_== extract1(text).toInt
    }
}