Code Examples for

ScalaCheck: The Definitive Guide

Return to chapter index

6 Generators in Detail

  • 6.1 Generator combinators
  • 6.2 Custom test case simplification
  • 6.3 Conclusion
  • 6.1 Generator combinators


    import org.scalacheck.Gen.choose val myGen = for { n <- choose(1, 50) m <- choose(n, 2*n) } yield (n, m)
    import org.scalacheck.Gen.choose import org.scalacheck.Prop.forAll val g = choose(-2, 5) val h = choose(4.1, 4.2) val p = forAll(h) { n => n >= 4.1 && n <= 4.2 }
    scala> g.sample res0: Option[Int] = Some(5) scala> h.sample res1: Option[Double] = Some(4.189116648661569) scala> p.check + OK, passed 100 tests.
    import org.scalacheck.Gen.negNum import org.scalacheck.Prop.forAll val propAbs = forAll(negNum[Int]) { n => math.abs(n) == -n }
    import org.scalacheck.Gen val genString = for { c1 <- Gen.numChar c2 <- Gen.alphaUpperChar c3 <- Gen.alphaLowerChar c4 <- Gen.alphaChar c5 <- Gen.alphaNumChar } yield List(c1,c2,c3,c4,c5).mkString
    scala> genString.sample res0: Option[java.lang.String] = Some(2MwMh) scala> genString.sample res1: Option[java.lang.String] = Some(3Fik3)
    import org.scalacheck.Gen val stringsGen = for { alpha <- Gen.alphaStr num <- Gen.numStr id <- Gen.identifier } yield (alpha.take(4), num.take(4), id.take(4))
    scala> println(stringsGen.sample) Some((vHoc,1991,xM1j))
    scala> org.scalacheck.Gen.fail.sample res0: Option[Nothing] = None
    import org.scalacheck.Gen.{sequence, choose, const} val numbers = sequence(List(choose(1,10), const(20), const(30)))
    scala> numbers.sample res0: Option[java.util.ArrayList[Int]] = Some([9, 20, 30])
    import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Gen.{choose, frequency} val evenNumberGen = for { n <- choose(2,100000) } yield 2*n val oddNumberGen = for { n <- choose(0,100000) } yield 2*n + 1 val numberGen = frequency( (1, oddNumberGen), (2, evenNumberGen), (4, 0) )
    import org.scalacheck.Prop.{forAll, collect} val propNumberGen = forAll(numberGen) { n => val l = { if (n == 0) "zero" else if (n % 2 == 0) "even" else "odd" } collect(l)(true) }
    scala> propNumberGen.check + OK, passed 100 tests. > Collected test data: 61% zero 26% even 13% odd
    scala> propNumberGen.check(10000) + OK, passed 10000 tests. > Collected test data: 57% zero 29% even 14% odd scala> propNumberGen.check(100000) + OK, passed 100000 tests. > Collected test data: 57% zero 29% even 14% odd
    import org.scalacheck.Gen.{oneOf, choose} val genNotZero = oneOf(choose(-10,-1), choose(1,10)) val genVowel = oneOf('a', 'e', 'i', 'o', 'u', 'y')
    def genUser(db: DB): Gen[User] = { val users: List[User] = db.getUsers Gen.oneOf(users) }
    import org.scalacheck.Gen.{ choose, listOf, nonEmptyListOf, listOfN, oneOf } import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Prop.forAll val genIntList = listOf(choose(0,10)) val genNonEmptyList = nonEmptyListOf(oneOf("foo", "bar")) val genEightBytes = listOfN(8, arbitrary[Byte]) val propIntsWithinBounds = forAll(genIntList) { xs => xs.forall { n:Int => n >= 0 && n <= 10 } } val propCorrectStrings = forAll(genNonEmptyList) { xs => (xs.size > 0) && xs.forall { s:String => s == "foo" || s == "bar" } } val propListLength = forAll(genEightBytes) { xs => xs.size == 8 }
    import org.scalacheck.Gen.{ choose, containerOf, nonEmptyContainerOf, containerOfN, oneOf } import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Prop.forAll val genIntList = containerOf[List,Int](choose(0,10)) val genNonEmptyList = nonEmptyContainerOf[List,String](oneOf("foo", "bar")) val genEightBytes = containerOfN[List,Byte](8, arbitrary[Byte])
    import org.scalacheck.Arbitrary.arbitrary val oddInt = arbitrary[Int] suchThat (_ % 2 != 0)
    scala> oddInt.sample res0: Option[Int] = Some(2068378103) scala> oddInt.sample res1: Option[Int] = None scala> oddInt.sample res2: Option[Int] = Some(-1)
    import org.scalacheck.Prop.{forAll,BooleanOperators} import org.scalacheck.Gen.choose def isPrime(n: Int): Boolean = n > 0 && (2 to n).forall(n % _ != 0) val p1 = forAll(choose(1,100)) { n => isPrime(n) ==> !isPrime(2*n) } val p2 = forAll(choose(1,100) suchThat isPrime) { n => !isPrime(2*n) }
    scala> p1.check ! Gave up after only 1 passed tests. 100 tests were discarded. scala> p2.check ! Gave up after only 0 passed tests. 101 tests were discarded.
    scala> (forAll(oddInt)(_ % 2 != 0)).check + OK, passed 100 tests.
    import org.scalacheck.Gen.listOf val listOfOddInt = listOf(oddInt) val p = forAll(listOfOddInt)(_.forall(_ % 2 != 0))
    scala> p.check ! Gave up after only 8 passed tests. 93 tests were discarded.
    import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Gen.listOf import org.scalacheck.Prop.forAll val oddInt = arbitrary[Int] retryUntil (_ % 2 != 0) val listOfOddInt = listOf(oddInt) val p = forAll(listOfOddInt)(_.forall(_ % 2 != 0))
    scala> p.check + OK, passed 100 tests.
    import org.scalacheck.Gen.{choose, oneOf} import org.scalacheck.Prop.forAll val p1 = forAll(choose(0,20)) { n => n > 10 } val p2 = forAll(oneOf(8,9,10,11)) { n => n > 10 }
    scala> p1.check ! Falsified after 1 passed tests. > ARG_0: 0 > ARG_0_ORIGINAL: 8 scala> p2.check ! Falsified after 0 passed tests. > ARG_0: 8 > ARG_0_ORIGINAL: 9
    import org.scalacheck.Gen.oneOf import org.scalacheck.Prop.forAll val p3 = forAll(oneOf(8,9,10,11) map (_ - 1)) { n => n > 10 }
    scala> p3.check Falsified after 0 passed tests. > ARG_0: 0 > ARG_0_ORIGINAL: 9
    import org.scalacheck.Gen.{someOf, pick} val numbers = someOf(List(1,2,3,4)) val twoStrings = pick(2, List("red", "blue", "green", "pink")) val numberLists = someOf(numbers, numbers, numbers)
    scala> numbers.sample res0: Option[Seq[Int]] = Some(ListBuffer(1, 2, 4)) scala> twoStrings.sample res1: Option[Seq[java.lang.String]] = Some(ListBuffer(red, pink)) scala> numberLists.sample res2: Option[Seq[Seq[Int]]] = Some(List( ListBuffer(1, 2, 3, 4), ListBuffer(1, 2, 3, 4), ListBuffer()))
    import org.scalacheck.Gen import org.scalacheck.Gen.{sized, listOfN} def genList[T](genElem: Gen[T]): Gen[List[T]] = { sized { sz: Int => for { listSize <- Gen.choose(0, sz) list <- Gen.listOfN(listSize, genElem) } yield list } }
    trait Tree[T] { def size: Int } case class Leaf[T]( item: T ) extends Tree[T] { def size = 1 } case class Node[T] ( children: List[Tree[T]] ) extends Tree[T] { def size = children.map(_.size).sum }
    import org.scalacheck.Gen import org.scalacheck.Gen.{oneOf, listOf} def genTree[T](genT: Gen[T]): Gen[Tree[T]] = oneOf(genLeaf(genT), genNode(genT)) def genLeaf[T](genT: Gen[T]): Gen[Leaf[T]] = genT map (Leaf(_)) def genNode[T](genT: Gen[T]): Gen[Node[T]] = for { children <- listOf(genTree(genT)) } yield Node(children)
    scala> import org.scalacheck.Arbitrary.arbitrary scala> val genIntTree = genTree(arbitrary[Int]) java.lang.StackOverflowError at org.scalacheck.Gen$class.map(Gen.scala:41) at org.scalacheck.Gen$$anon$3.map(Gen.scala:51) at .genLeaf(<console>:23) at .genTree(<console>:19) at $anonfun$genNode$1.apply(<console>:28) at $anonfun$genNode$1.apply(<console>:28)
    import org.scalacheck.Gen import org.scalacheck.Gen.{oneOf, listOf, lzy} def genTree[T](genT: Gen[T]): Gen[Tree[T]] = lzy { oneOf(genLeaf(genT), genNode(genT)) } def genLeaf[T](genT: Gen[T]): Gen[Leaf[T]] = genT.map(Leaf(_)) def genNode[T](genT: Gen[T]): Gen[Node[T]] = for { children <- listOf(genTree(genT)) } yield Node(children)
    scala> import org.scalacheck.Arbitrary.arbitrary scala> val genIntTree = genTree(arbitrary[Int]) genIntTree: org.scalacheck.Gen[Tree[Int]] = ...
    scala> genIntTree.sample res0: Option[Tree[Int]] = Some(Leaf(-2147483648)) scala> genIntTree.sample res1: Option[Tree[Int]] = Some(Leaf(0)) scala> genIntTree.sample java.lang.StackOverflowError at org.scalacheck.Gen$$anonfun$1$$anonfun$apply... at org.scalacheck.Gen$$anonfun$1$$anonfun$apply... at scala.collection.LinearSeqOptimized$class.fo... at scala.collection.immutable.List.foldLeft(Lis... at org.scalacheck.Gen$$anonfun$1.apply(Gen.scal... at org.scalacheck.Gen$$anonfun$1.apply(Gen.scal...
    import org.scalacheck.Gen import org.scalacheck.Gen.{sized, choose, resize, listOfN, oneOf, lzy} def genTree[T](genT: Gen[T]): Gen[Tree[T]] = lzy { oneOf(genLeaf(genT), genNode(genT)) } def genLeaf[T](genT: Gen[T]): Gen[Leaf[T]] = genT.map(Leaf(_)) def genNode[T](genT: Gen[T]): Gen[Node[T]] = sized { size => for { s <- choose(0, size) g = resize(size / (s+1), genTree(genT)) children <- listOfN(s, g) } yield Node(children) }
    scala> import org.scalacheck.Arbitrary.arbitrary scala> val genIntTree = genTree(arbitrary[Int]) scala> genIntTree.sample res0: Option[Tree[Int]] = Some(Node(List( Leaf(-567182617), Node(List(Leaf(0))), Leaf(2147483647), Leaf(953266546), Node( List(Node(List()))), Leaf(896351754), ...

    6.2 Custom test case simplification


    trait Expression { override def toString = show(this) } case class Const(n: Int) extends Expression case class Add( e1: Expression, e2: Expression ) extends Expression case class Mul( e1: Expression, e2: Expression ) extends Expression def eval(e: Expression): Int = e match { case Const(n) => n case Add(e1,e2) => eval(e1) + eval(e2) case Mul(e1,e2) => eval(e1) * eval(e2) } def show(e: Expression): String = e match { case Const(n) => n.toString case Add(e1,e2) => "("+show(e1)+" + "+show(e2)+")" case Mul(e1,e2) => "("+show(e1)+" * "+show(e2)+")" }
    import org.scalacheck.Gen val genExpr: Gen[Expression] = Gen.sized { sz => Gen.frequency( (sz, genConst), (sz - math.sqrt(sz).toInt, Gen.resize(sz/2, genAdd)), (sz - math.sqrt(sz).toInt, Gen.resize(sz/2, genMul)) ) } val genConst = Gen.choose(0, 10).map(Const(_)) val genAdd = for { e1 <- genExpr; e2 <- genExpr } yield Add(e1, e2) val genMul = for { e1 <- genExpr; e2 <- genExpr } yield Mul(e1, e2)
    scala> genExpr.sample res0: Option[Expression] = Some((9 + (3 + ((0 * (3 + 1)) + 3))))
    def rewrite(e: Expression): Expression = e match { case Add(e1, e2) if e1 == e2 => Mul(Const(2), e1) case Mul(Const(0), e) => Const(0) case Add(Const(1), e) => e case _ => e }
    import org.scalacheck.Prop.forAll val propRewrite = forAll(genExpr) { expr => eval(rewrite(expr)) == eval(expr) }
    scala> propRewrite.check ! Falsified after 40 passed tests. > ARG_0: (1 + (((1 + 10) + 10) * (2 + 0))) scala> propRewrite.check + OK, passed 100 tests. scala> propRewrite.check(1000) ! Falsified after 313 passed tests. > ARG_0: (1 + ((8 * ((1 + 0) * (10 * 7))) + (6 * 0)))
    trait Shrink[T] { def shrink(x: T): scala.collection.immutable.Stream[T] }
    scala> org.scalacheck.Shrink.shrink(10) res0: Stream[Int] = Stream(0, ?) scala> res0.print 0, 5, -5, 8, -8, 9, -9, empty
    import org.scalacheck.Shrink import org.scalacheck.Shrink.shrink import scala.collection.immutable.Stream implicit val shrinkExpr: Shrink[Expression] = Shrink({ case Const(n) => shrink(n) map Const case Add(e1, e2) => Stream.concat( Stream(e1, e2), shrink(e1) map (Add(_, e2)), shrink(e2) map (Add(e1, _)) ) case Mul(e1, e2) => Stream.concat( Stream(e1, e2), shrink(e1) map (Mul(_, e2)), shrink(e2) map (Mul(e1, _)) ) })
    scala> org.scalacheck.Shrink.shrink[Expression](Const(10)) res0: Stream[Expression] = Stream(0, ?) scala> res0.print 0, 5, -5, 8, -8, 9, -9, empty
    import org.scalacheck.Prop.{forAll, AnyOperators} val propRewrite = forAll(genExpr) { expr => val rexpr = rewrite(expr) (eval(rexpr) ?= eval(expr)) :| "rewritten = "+rexpr }
    scala> propRewrite.check(1000) ! Falsified after 43 passed tests. > Labels of failing property: Expected 1 but got 0 rewritten = 0 > ARG_0: (1 + 0) > ARG_0_ORIGINAL: (1 + (6 * (7 * ((4 + 0) * (9 + 1))))) scala> propRewrite.check(1000) ! Falsified after 121 passed tests. > Labels of failing property: Expected 1 but got 0 rewritten = 0 > ARG_0: (1 + 0) > ARG_0_ORIGINAL: (1 + (6 * (7 * 0)))

    6.3 Conclusion

    For more information about ScalaCheck: The Definitive Guide, please visit:

    http://www.artima.com/shop/scalacheck

    and:

    http://booksites.artima.com/scalacheck

    Copyright © 2013-2014 Artima, Inc. All rights reserved.

    Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.