Code Examples for

ScalaCheck: The Definitive Guide

Return to chapter index

4 Designing Properties

  • 4.1 Incomplete properties
  • 4.2 Relation properties
  • 4.3 Reference implementations
  • 4.4 Restricting test cases
  • 4.5 Round-trip properties
  • 4.6 Constructing optimal output
  • 4.7 Conclusion
  • 4.1 Incomplete properties


    property("no negative output") = forAll { (m: Int, n: Int) => val result: Int = myMagicMathOperation(m, n) result >= 0 } property("actually compressed") = forAll { xs: List[String] => val result: List[String] = myCompressor(xs) result.length <= xs.length } property("is monotonic") = forAll { (n1: Float, n2: Float) => val r1 = myMonotonicFunction(n1) val r2 = myMonotonicFunction(n2) if (n1 > n2) r1 >= r2 else r1 <= r2 }

    4.2 Relation properties


    import org.scalacheck.{Gen, Prop} // A tweet generator val genTweet: Gen[String] = ... // The function under test def rankTweet(tweet: String): Int = ... val propRankTweet = Prop.forAll(genTweet) { tweet => val score = rankTweet(tweet) ... }
    val propRankTweet = Prop.forAll(genTweet, genTweet) { (tweet1,tweet2) => val score1 = rankTweet(tweet1) val score2 = rankTweet(tweet2) if (tweet1.length <= tweet2.length) score1 >= score2 else score1 < score2 }

    4.3 Reference implementations


    import java.util.HashMap import collection.JavaConversions._ import collection.immutable.IntMap def equalMaps(hm: HashMap[Int,Any], im: IntMap[Any]) = { im.keys.forall(hm.containsKey) && hm.keySet.containsAll(im.keys) && im.keys.forall(k => im(k) == hm(k)) } import org.scalacheck.Gen import org.scalacheck.Arbitrary.arbitrary val genMaps: Gen[(HashMap[Int,Any],IntMap[Any])] = arbitrary[List[Int]] map { xs => val mappings = for(n <- xs) yield (n, new Object) val im = IntMap(mappings: _*) val hm = new HashMap[Int, Any] for((n,x) <- mappings) hm.put(n,x) (hm,im) }
    import org.scalacheck.Prop.{forAll, AnyOperators} import org.scalacheck.Properties object IntMapSpec extends Properties("IntMap") { property("size") = forAll(genMaps) { case (hm, im) => im.size ?= hm.size } property("isEmpty") = forAll(genMaps) { case (hm,im) => im.isEmpty ?= hm.isEmpty } property("add") = forAll(genMaps) { case (hm,im) => forAll { (k: Int, v: String) => hm.put(k, v) equalMaps(hm, im + (k -> v)) } } }

    4.4 Restricting test cases


    import org.scalacheck.Prop.{forAll, BooleanOperators} val propSqrt = forAll { n: Int => (n >= 0) ==> { val m = math.sqrt(n) math.round(m*m) == n } }
    import org.scalacheck.Prop.{forAll, BooleanOperators} val propSlice = forAll { (xs: List[Int], n: Int, m: Int) => (m >= n && xs.indices.contains(n) && xs.indices.contains(m) ) ==> { val s = xs.slice(n,m) s.length == (m-n) && xs.containsSlice(s) } }
    scala> propSlice.check ! Gave up after only 2 passed tests. 99 tests were discarded.
    import org.scalacheck.Prop.{forAll, BooleanOperators} import org.scalacheck.Gen.oneOf val propSlice = forAll { xs: List[Int] => forAll(oneOf(xs.indices), oneOf(xs.indices)) { (m, n) => m >= n ==> { val s = xs.slice(n,m) s.length == (m-n) && xs.containsSlice(s) } } }
    scala> propSlice.check + OK, passed 100 tests.

    4.5 Round-trip properties


    property("reverse inverse") = Prop.forAll { xs: List[Int] => xs.reverse.reverse == xs }
    import org.scalacheck.Prop.forAll def encodeBin(n: Int): String = n match { case 0 => "0" case 1 => "1" case n if n % 2 == 1 => encodeBin(n / 2) + "1" case n => encodeBin(n / 2) + "0" } def decodeBin(s: String): Int = if (s.isEmpty) 0 else s.last match { case '0' => 2 * decodeBin(s.substring(0,s.length-1)) case '1' => 2 * decodeBin(s.substring(0,s.length-1)) + 1 } val propDecodeEncode = forAll { n: Int => decodeBin(encodeBin(n)) == n }
    scala> propDecodeEncode.check ! Falsified after 2 passed tests. > ARG_0: -1 > ARG_0_ORIGINAL: -927439645
    import org.scalacheck.Prop.{forAll, BooleanOperators} val propDecodeEncode = forAll { n: Int => n >= 0 ==> (decodeBin(encodeBin(n)) == n) }
    type AST = ... def parse(s: String): AST = ... def prettyPrint(ast: AST): String = ... val astGenerator: Gen[AST] = ... val prop = forAll(astGenerator) { ast => parse(prettyPrint(ast)) == ast }

    4.6 Constructing optimal output


    def runlengthEnc[A](xs: List[A]): List[(Int,A)] = ...
    import org.scalacheck.Gen import Gen.{choose, alphaNumChar, sized} val genOutput: Gen[List[(Int,Char)]] = { def rleItem: Gen[(Int,Char)] = for { n <- choose(1,20) c <- alphaNumChar } yield (n,c) def rleList(size: Int): Gen[List[(Int,Char)]] = { if (size <= 1) rleItem.map(List(_)) else for { tail@(_,c1)::_ <- rleList(size-1) head <- rleItem retryUntil (_._2 != c1) } yield head :: tail } sized(rleList) }
    def runlengthDec[A](r: List[(Int,A)]): List[A] = r flatMap { case (n,x) => List.fill(n)(x) }
    val p = Prop.forAll(genOutput) { r => runlengthEnc(runlengthDec(r)) == r }

    4.7 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.