Last active
December 19, 2023 04:45
-
-
Save nelanka/ca0e8eeee22901ef860c to your computer and use it in GitHub Desktop.
ScalaCheck Examples
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It seems
org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
replacedGeneratorDrivenPropertyChecks
. Verified with scalatest 3.2.17 and scalacheck 3.2.17.0.