Specs2を試す(7)
ScalaCheck values
Given/When/Then シーケンスで scalacheck の generator を使う方法。
import org.scalacheck._ import org.scalacheck.Gen._ import org.scalacheck.Prop._ import org.specs2._ import org.specs2.specification.gen._ class GivenWhenThenScalacheckSpec extends Specification with ScalaCheck { def is = "A given-when-then example for a calculator" ^ "Given a first number n1" ^ number1 ^ "And a second number n2" ^ number2 ^ "When I add them" ^ add ^ "Then I should get n1 + n2" ^ result ^ end object number1 extends Given[Int] { def extract(text: String) = choose(-10, 10) } object number2 extends When[Int, (Int, Int)] { def extract(number1: Int, text: String) = for { n2 <- choose(-10, 10) } yield (number1, n2) } object add extends When[(Int, Int), Addition] { def extract(numbers: (Int, Int), text: String) = Addition(numbers._1, numbers._2) } object result extends Then[Addition] { def extract(text: String)(implicit op: Arbitrary[Addition]) = { check { (op: Addition) => op.calculate must_== op.n1 + op.n2 } } } case class Addition(n1: Int, n2: Int) { def calculate: Int = n1 + n2 } }
通常はGiven/When/Thenのステップクラスはorg.specs2.specificationの下のものをimportして使うけれど
scalacheckを使うときはorg.specs2.specification.gen._ のものを使う。
number1, number2 の extract メソッドは scalacheck の generator(Gen[T]) という型を返している。
一方、add の extract メソッドは Addition型を返しているが、T から Gen[T] への暗黙変換が効く。
ちなみにこれは specs2 ではなく scalacheck のほうに定義してある。
ScalaCheck トレイトは check メソッドを使うために mixin されている。
check メソッドに関数を渡すとその結果が scalacheck の Prop 型として返ってくる。
それがさらに specs2 の Result 型に変換される仕組み。
Then の extract メソッドには implicit な Arbitrary[T] の引数があるが、これは
ScalaCheck 側でプロパティを生成するのに使っている。この変の仕組みは specs2 ではなく scalacheck のほうを見たほうが良い。
とまあこれだけを理解するのにspecs2とscalacheckのソースと3時間くらいにらめっこしました。修行が足りない。