A recent example of using this came from this StackOverflow question. It required a largish number of test cases, so it seemed a perfect fit to try out ScalaCheck.
The code to test was:
package blevins.example
object BigIntEncoder {
val radix = 36
implicit def byteArrayToString(ba: Array[Byte]): String = {
new java.math.BigInteger(ba).toString(radix)
}
implicit def stringToByteArray(s: String): Array[Byte] = {
new java.math.BigInteger(s, radix).toByteArray
}
}
To test, you write something like this:
package blevins.example
import org.scalacheck._
object BigIntEncoderSpecification extends Properties("BigIntEncoder") {
import Prop._
import Gen._
def genArray( fill: Array[Byte] => Unit) = Gen.sized { size =>
val bytes: Array[Byte] = new Array[Byte](size)
fill(bytes)
bytes
} suchThat (_.size > 0)
def filledByteArray( i: int ) = genArray(bytes =>
for (i <- 0 to bytes.length - 1) { bytes(i) = i.asInstanceOf[Byte] }
)
val arbByteArray = genArray(bytes => scala.util.Random.nextBytes(bytes))
val zeroByteArray = filledByteArray( 0x00 )
val fullByteArray = filledByteArray( 0xff )
def checkEncodingRoundTrip = { (ba: Array[Byte]) =>
import BigIntEncoder._
ba deepEquals stringToByteArray(byteArrayToString(ba))
}
property("random") = forAll(arbByteArray)(checkEncodingRoundTrip)
property("zero") = forAll(zeroByteArray)(checkEncodingRoundTrip)
property("full") = forAll(fullByteArray)(checkEncodingRoundTrip)
}
This code will generate random, all-zero, and all-binary-ones (0xff) arrays and test their equality using the checkEncodingRoundTrip method, which exercises the code to be tested. It failed sometimes for the random array, and failed all the time for the other two types. I also noticed that the random array failed when the first byte of the arrays was either 0x00 or 0xff. I assumed that this was a side effect of BigInteger taking the two-complement of the byte array, so I padded it like so:
package blevins.example
object BigIntEncoder {
val radix = 36
implicit def byteArrayToString(ba: Array[Byte]): String = {
new java.math.BigInteger(addByte(ba)).toString(radix)
}
implicit def stringToByteArray(s: String): Array[Byte] = {
stripByte(new java.math.BigInteger(s, radix).toByteArray)
}
def addByte(ba: Array[Byte]): Array[Byte] = {
val h = new Array[Byte](1)
h(0) = 0x01
h ++ ba
}
def stripByte(ba: Array[Byte]): Array[Byte] = {
ba.slice(1,ba.size)
}
}
This produced the desired results (all passing), but a large amount of output. I wrote my own output-generator like this:
package blevins.example
object Test extends Application {
import org.scalacheck._
import org.scalacheck.Test._
val params = Params(5000, 10, 1, 100, new java.util.Random(), 1, 1)
val props = checkProperties(BigIntEncoderSpecification, params)
for ((name, result) <- props) {
println(name + ": " + result.status)
}
}
This also increased the number of tests-per-property from 100 to 5000.
My output was:
BigIntEncoder.random: Passed
BigIntEncoder.zero: Passed
BigIntEncoder.full: Passed
Yay! Now go watch TV.