A property is the testable unit in ScalaCheck, and is represented by the org.scalacheck.Prop
class.
There are several ways to create properties in ScalaCheck, one of them is to use the org.scalacheck.Prop.forAll
method like in the example below.
scala> val propConcatLists = forAll { (l1: List[Int], l2: List[Int]) =>
l1.size + l2.size == (l1 ::: l2).size }
We can use the check
method to test it:
scala> propConcatLists.check
+ OK, passed 100 tests.
OK, that seemed alright. Now, we'll define another property.
scala> val propSqrt = forAll { (n: Int) => scala.math.sqrt(n*n) == n }
And check it:
scala> propSqrt.check
! Falsified after 2 passed tests.
> ARG_0: -1
> ARG_0_ORIGINAL: -488187735
Not surprisingly, the property doesn't hold. The argument -1
falsifies it. You can also see that the argument
-488187735
falsifies the property. That was the first argument ScalaCheck found, and it was then
simplified to
-1
.
The forAll
method creates universally quantified properties directly, but it is also
possible to create new properties by combining other properties, or to use any of the specialised
methods in the org.scalacheck.Prop
object.
As mentioned before, org.scalacheck.Prop.forAll
creates universally quantified properties.
forAll
takes a function as parameter, and creates a property out of it that can be tested with the check
method or with Scalatest (using Checkers trait), like in these examples.
The function passed to forAll
should return Boolean
or another property, and can take parameters of any types,
as long as there exist implicit Arbitrary
instances for the types.
By default, ScalaCheck has instances for common types like Int
, String
, List
, etc, but it is also possible
to define your own Arbitrary
instances.
For example:
import org.scalacheck.Prop.forAll
check {
forAll((s1: String, s2: String) => (s1 + s2).endsWith(s2) == res0)
}
When you run check
on the properties, ScalaCheck generates random instances of the function parameters and
evaluates the results, reporting any failing cases.
You can also give forAll
a specific data generator. In the following example smallInteger
defines a generator
that generates integers between 0
and 100
, inclusively.
This way of using the forAll
method is good to use when you want to control the data generation by specifying
exactly which generator that should be used, and not rely on a default generator for the given type.
import org.scalacheck.Gen
import org.scalacheck.Prop.forAll
val smallInteger = Gen.choose(0, 100)
check {
forAll(smallInteger)(n => (n >= 0 && n <= 100) == res0)
}
Sometimes, a specification takes the form of an implication. In ScalaCheck, you can use the implication
operator ==>
to filter the generated values.
If the implication operator is given a condition that is hard or impossible to fulfill, ScalaCheck might
not find enough passing test cases to state that the property holds. In the following trivial example,
all cases where n
is non-zero will be thrown away:
scala> import org.scalacheck.Prop.{forAll, propBoolean}
scala> val propTrivial = forAll { n: Int =>
| (n == 0) ==> n + 10 == 10
| }
scala> propTrivial.check
! Gave up after only 4 passed tests. 500 tests were discarded.
It is possible to tell ScalaCheck to try harder when it generates test cases, but generally you should try to refactor your property specification instead of generating more test cases, if you get this scenario.
Using implications, we realise that a property might not just pass or fail, it could also be undecided if the implication condition doesn't get fulfilled.
In this example, ScalaCheck will only care for the cases when n
is an even number.
import org.scalacheck.Prop.{ forAll, propBoolean }
check {
forAll { n: Int => (n % 2 == 0) ==> (n % 2 == res0) }
}
A third way of creating properties, is to combine existing properties into new ones.
val p1 = forAll(...)
val p2 = forAll(...)
val p3 = p1 && p2
val p4 = p1 || p2
val p5 = p1 == p2
val p6 = all(p1, p2) // same as p1 && p2
val p7 = atLeastOne(p1, p2) // same as p1 || p2
Here, p3
will hold if and only if both p1
and p2
hold, p4
will hold if either p1
or p2
holds,
and p5
will hold if p1
holds exactly when p2
holds and vice versa.
import org.scalacheck.Gen
import org.scalacheck.Prop.forAll
val smallInteger = Gen.choose(0, 100)
check {
forAll(smallInteger) { n =>
(n > 100) == res0
} &&
forAll(smallInteger)(n => (n >= 0) == res1)
}
Often you want to specify several related properties, perhaps for all methods in a class.
ScalaCheck provides a simple way of doing this, through the Properties
trait.
Look at the following specifications that define some properties for zero.
You can use the check method of the Properties
class to check all specified properties,
just like for simple Prop
instances. In fact, Properties
is a subtype of Prop
,
so you can use it just as if it was a single property.
That single property holds if and only if all of the contained properties hold.
import org.scalacheck.{ Prop, Properties }
class ZeroSpecification extends Properties("Zero") {
import org.scalacheck.Prop.{ forAll, propBoolean }
property("addition property") = forAll { n: Int => (n != 0) ==> (n + res0 == n) }
property("additive inverse property") = forAll { n: Int => (n != 0) ==> (n + (-n) == res1) }
property("multiplication property") = forAll { n: Int => (n != 0) ==> (n * res2 == 0) }
}
check(Prop.all(new ZeroSpecification().properties.to(List).map(_._2): _*))