ScalaCheck のジェネレータをムリクリ突っ込んでみた
まず、(ScalaCheck)http://www.scalacheck.org/というのがテストツールの一種。
ブラックボックステスト用のツールで、ランダムに値を生成して関数の挙動を見るというものだ。
sbt 定義は
scalaVersion := "2.10.2" resolvers ++= Seq( "Sonatype OSS" at "https://oss.sonatype.org/content/repositories/releases" ) libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.11.1"
使い方は公式のサンプルを見ればわかると思う。
import org.scalacheck.Properties import org.scalacheck.Prop.forAll object StringSpecification extends Properties("String") { property("startsWith") = forAll { (a: String, b: String) => (a+b).startsWith(a) } property("concatenate") = forAll { (a: String, b: String) => (a+b).length > a.length && (a+b).length > b.length } property("substring") = forAll { (a: String, b: String, c: String) => (a+b+c).substring(a.length, a.length+b.length) == b } }
で、これのテストで使われてる乱数なりランダムなデータってどうやって生成されてるのか?と思って追いかけた。
で、結論から言うと org.scalacheck.Gen[+T]
が生成している。つまり、これを実装してしまえばカスタムなジェネレータが設定できるわけだ。
ってことで、カスタムなものを作ってみようと思うのだが、まともに実装するのがつらい。
と思うと、便利な関数を発見した。
private[scalacheck] def gen[T](f: P => R[T]): Gen[T] = new Gen[T] { def doApply(p: P) = f(p) }
で、R[T] は object.Gen
のパッケージ参照制約付きのインナートレイト。じゃぁやることは決まってて、
package org.scalacheck case class Dummy(id:Int) object CustomGenerator { private def generateDummy(parameters:Gen.Parameters):Gen.R[Dummy] = new Gen.R[Dummy] { protected def result: Option[Dummy] = { Some(Dummy(parameters.rng.nextInt())) } } def dummyGen:Gen[Dummy] = Gen.gen(generateDummy) }
いざ行かんだみーじぇねれーた。
で、さくっとテスる
import org.scalacheck.{CustomGenerator, Properties} import org.scalacheck.Prop.forAll object CustomGeneratorSpecification extends Properties("DummyRun") { property("sample") = forAll(CustomGenerator.dummyGen) { value => value.id != 0 } }
で、実行
[info] Compiling 1 Scala source to D:\sources\ScalaCheck\target\scala-2.10\classes... [info] Compiling 1 Scala source to D:\sources\ScalaCheck\target\scala-2.10\test-classes... [info] + DummyRun.sample: OK, passed 100 tests. [info] Passed: Total 1, Failed 0, Errors 0, Passed 1 [success] Total time: 5 s, completed 2013/11/26 22:19:21
ヒャッハー
ついでに forAll のもう一つの定義が
def forAll[A1,P] (
f: A1 => P)(implicit
p: P => Prop,
a1: Arbitrary[A1], s1: Shrink[A1], pp1: A1 => Pretty
): Prop = forAllShrink(arbitrary[A1],shrink[A1])(f andThen p)
なので、Arbitrary を実装すればいいんだな!
まぁでも既に Gen 出来てるなら楽勝
def dummyGen:Gen[Dummy] = Gen.gen(generateDummy) implicit val dummyArbitrary = Arbitrary(dummyGen)
つーことで
import org.scalacheck.{Dummy, CustomGenerator, Properties} import org.scalacheck.Prop.forAll object CustomGeneratorSpecification extends Properties("DummyRun") { import CustomGenerator._ def sample(dummy:Dummy) = dummy.id != 0 property("sample") = forAll { (a1:Dummy) => a1.id != 0 } }
ウェーイ