Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get ScalaCheck's Arbitrary to always generate some special case values?

I'd like to have all my properties always be tested with at least a fixed set of special values in addition to some random values. I'd like to define this in my generator specification, not in every test using that generator type. For instance, if I were generating Ints, I'd like my generator to always generate at least 0, 1 and -1 for each test case. Is this possible?

The best I've come up with so far is to make a sized generator where the smallest n sizes correspond to my n special cases. This is problematic at least because all possible sizes are not tested when the max number of tests is configured to be lower than the max size parameter.

like image 656
mpartel Avatar asked Sep 27 '11 12:09

mpartel


2 Answers

First of all, there already is a bias in Scalacheck so that 0, 1, -1, Int.MaxValue and Int.MinValue are very likely to be choosen in addition to other Int values. So, if that's your worry, don't worry about it. Likewise, empty strings are likely to be generated.

But, if you want to reproduce this behavior for something else, use Gen.oneOf or Gen.frequency, perhaps in conjunction with Gen.choose. Since oneOf and frequency take Gen as parameter, you can combine special cases with generic generators.

For example:

val myArb: Arbitrary[Int] = Arbitrary(Gen.frequency(
    1 -> -1, 
    1 ->  0, 
    1 -> 1, 
    3 -> Arbitrary.arbInt.arbitrary
))

Does pretty much what you asked for, with 50% chance of arbitrary ints (which will come with the bias I spoke of), and 16.6% for each of -1, 0 and 1.

like image 70
Daniel C. Sobral Avatar answered Oct 25 '22 16:10

Daniel C. Sobral


I had the same question today & ended up here so thought I'd add my solution which was to generate Props of my special cases prior to using a Gen, like so:

import org.scalacheck.Gen.{alphaChar, const}
import org.scalacheck.Prop.{forAll, passed}
import org.scalacheck.{Gen, Prop}

// evaluate fn first with some initial values, then with some generated ones
def forAllAfter[A](init: A*)(subsequent: Gen[A])(fn: A => Prop): Prop =
  init.foldLeft(passed) { case (p, i) => p && forAll(const(i))(fn) } && forAll(subsequent)(fn) 

// example of usage
val prop = forAllAfter('a', 'b', 'c')(alphaChar) { c =>
  println(c) 
  passed
}

The forAllAfter function here first creates Props using Gen.const for each value that must be tested then combines them with props created using a generator of subsequent values to test.

If you're using ScalaTest is that you then need to mix the Checkers trait into your test to evaluate the resultant Prop like so:

import org.scalatest.WordSpec
import org.scalatest.prop.Checkers
import org.scalacheck.Gen.{alphaChar, const}
import org.scalacheck.Prop.{forAll, passed}
import org.scalacheck.{Gen, Prop}

class TestExample extends WordSpec with Checkers {

  def forAllAfter[A](init: A*)(subsequent: Gen[A])(fn: A => Prop): Prop =
    init.foldLeft(passed) { case (p, i) => p && forAll(const(i))(fn) } && forAll(subsequent)(fn)

  val prop: Prop = forAllAfter('a', 'b', 'c')(alphaChar) { c =>
    println(c)
    passed
  }

  "Test example" should {
    "Work correctly" in {
      check(prop)
    }
  }
}
like image 30
Tom Avatar answered Oct 25 '22 17:10

Tom