Skip to content

Instantly share code, notes, and snippets.

@nelanka
Last active December 19, 2023 04:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nelanka/ca0e8eeee22901ef860c to your computer and use it in GitHub Desktop.
Save nelanka/ca0e8eeee22901ef860c to your computer and use it in GitHub Desktop.
ScalaCheck Examples
import org.scalacheck.Prop.BooleanOperators
import org.scalacheck.{Arbitrary, Gen}
import org.scalatest.prop.GeneratorDrivenPropertyChecks
import org.scalatest.{FreeSpec, Matchers}
/**
* Some example usages of the (Scala Check)[http://scalacheck.org] library
*
* References:
* (Scala Check User Guide)[https://github.com/rickynils/scalacheck/wiki/User-Guide]
* (Scala Test Property Based Testing User Guide)[http://www.scalatest.org/user_guide/generator_driven_property_checks]
* (Example Gist)[https://gist.github.com/miguel-vila/7330675]
*/
class ScalaCheckExamples extends FreeSpec with Matchers with GeneratorDrivenPropertyChecks {
"Universal property - One that holds for all values" - {
forAll { (a: String, b: String) =>
a.length + b.length should equal((a + b).length)
}
}
"Optionally, you can name the arguments so you get meaningful failure messages" - {
forAll("first", "second") { (a: String, b: String) =>
a.length + b.length should equal((a + b).length)
}
}
"Conditional property - One that holds for a subset of values" - {
forAll { n: Int =>
(n >= 0 && n < 10000) ==> (List.fill(n)("").length == n)
}
}
"In ScalaTest, you can use 'whenever' instead of '==>'" - {
forAll { n: Int =>
whenever(n >= 0 && n < 10000) {
List.fill(n)("").length == n
}
}
}
/* Generators
The argument list for the function you pass to forAll needs matching generators, usually
in implicit scope to generate random values for. These are defined for all the scala types
lists, and a few others.
You can define your own generators and use them directly or put them in implicit scope
forAll(generator){ property check }
*/
"Generators" - {
// You can choose from a range of values:
Gen.choose(0, 100)
// You could map on a generator to get even numbers
Gen.choose(0, 100).map(_ * 2)
// From a list of values
Gen.oneOf("A", "B", "C")
Gen.oneOf(List("A", "B", "C"))
Gen.oneOf('A', 'E', 'I', 'O', 'U', 'Y')
// Provide a frequency distribution
val vowelGen = Gen.frequency(
(3, 'A'),
(4, 'E'),
(2, 'I'),
(3, 'O'),
(1, 'U'),
(1, 'Y'))
// Combine using a comprehension
val pairGen = for {
n <- Gen.choose(10,20)
m <- Gen.choose(2*n, 500)
} yield (n,m)
// Conditional generators using suchThat
val smallEvenIntegerGen = Gen.choose(0,200) suchThat (_ % 2 == 0)
// Generating containers
val intListGen = Gen.containerOf[List,Int](Gen.oneOf(1, 3, 5))
val stringStreamGen = Gen.containerOf[Stream,String](Gen.alphaStr)
val boolArrayGen = Gen.containerOf[Array,Boolean](true)
// Ex: You could in addition define generators for the numerator and denominator that only produce valid values, like this:
val validNumbersGen = for (n <- Gen.choose(Integer.MIN_VALUE + 1, Integer.MAX_VALUE)) yield n
val validDenomsGen = for (d <- validNumbersGen if d != 0) yield d
// You could then use them in the property check like this:
forAll (validNumbersGen, validNumbersGen) { (n: Int, d: Int) =>
// See note below about why we need 'whenever' here
whenever (d != 0 && d != Integer.MIN_VALUE && n != Integer.MIN_VALUE) {
// Check some fraction
???
}
}
// Supplying both generators and argument names
forAll ((validNumbersGen, "n"), (validNumbersGen, "d")) { (n: Int, d: Int) =>
// See note below about why we need 'whenever' here
whenever (d != 0 && d != Integer.MIN_VALUE && n != Integer.MIN_VALUE) {
// Check some fraction
???
}
}
/*
Note that even if you use generators that don't produce the invalid values,
you still need the whenever clause. The reason is that once a property fails,
ScalaCheck will try and shrink the values to the smallest values that still
cause the property to fail. During this shrinking process ScalaCheck may pass
invalid values. The whenever clause is still needed to guard against those
values. (The whenever clause also clarifies to readers of the code exactly
what the property is in a succinct way, without requiring that they find and
understand the generator definitions.)
*/
/* Sized Generators
When ScalaCheck uses a generator to generate a value, it feeds it with some parameters.
One of the parameters the generator is given, is a size value, which some generators use
to generate their values. If you want to use the size parameter in your own generator,
you can use the Gen.sized method:
*/
def matrix[T](g: Gen[T]): Gen[Seq[Seq[T]]] = Gen.sized { size =>
val side = scala.math.sqrt(size.toDouble).asInstanceOf[Int]
Gen.listOfN(side, Gen.listOfN(side, g))
}
/* Arbitrary Generators
Arbitrary is used to supply an implicit Generator.
The factory method `Arbitrary(...)` takes a generator of type `Gen[T]` and returns an instance of `Arbitrary[T]
It basically wraps a Generator and only those, now Arbitrary types, are looked for in implicit scope
*/
sealed trait Side
case object BUY extends Side
case object SELL extends Side
implicit val arbSide: Arbitrary[Side] = Arbitrary(Gen.oneOf(BUY, SELL))
def checkSide(side: Side) = true
forAll((s: Side) => checkSide(s))
// Random values - You can use any generator to get a "sample", a single value, to use for tests
intListGen.sample
}
}
@asarkar
Copy link

asarkar commented Dec 19, 2023

It seems org.scalatestplus.scalacheck.ScalaCheckPropertyChecks replaced GeneratorDrivenPropertyChecks. Verified with scalatest 3.2.17 and scalacheck 3.2.17.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment