## 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 }