Skip to content

Instantly share code, notes, and snippets.

@davidallsopp
Last active September 16, 2020 14:07
Show Gist options
  • Save davidallsopp/60d7474a1fe8dc9b1f2d to your computer and use it in GitHub Desktop.
Save davidallsopp/60d7474a1fe8dc9b1f2d to your computer and use it in GitHub Desktop.
Examples of writing mixed unit/property-based (ScalaTest with ScalaCheck) tests. Includes tables and generators as well as 'traditional' tests.
/**
* Examples of writing mixed unit/property-based (ScalaCheck) tests.
*
* Includes tables and generators as well as 'traditional' tests.
*
* @see http://www.scalatest.org/user_guide/selecting_a_style
* @see http://www.scalatest.org/user_guide/property_based_testing
*/
import org.scalatest._
import prop._
import scala.collection.immutable._ // For BitSet
import java.awt.Color
/**
* This is the "scalatest" style of property-driven tests, which aims to be more consistent with the other ScalaTest
* styles. Note that the syntax has subtle differences from pure ScalaCheck, e.g.
*
* property("name") {forAll {}} rather than
* property("name") = forAll {}
*/
class Example extends PropSpec with PropertyChecks with ShouldMatchers {
val examples =
Table(
"set",
BitSet.empty,
HashSet.empty[Int],
TreeSet.empty[Int])
// table-driven
property("an empty Set should have size 0") {
//forAll(examples) { set => set.size should be(0) }
forAll(examples) { _.size should be(0) }
}
// table-driven, expecting exceptions
property("invoking head on an empty set should produce NoSuchElementException") {
forAll(examples) { set =>
a[NoSuchElementException] should be thrownBy { set.head }
}
}
// table-driven, expecting exceptions, with an alternative syntax
property("again, invoking head on an empty set should produce NoSuchElementException") {
forAll(examples) { set =>
evaluating { set.head } should produce[NoSuchElementException]
}
}
// A 2-column table
val colours = Table(
("Name", "Colour"), // First tuple is the title row
("r", Color.RED),
("g", Color.GREEN),
("b", Color.BLUE))
property("colours") {
forAll(colours) { (name: String, colour: Color) =>
colour.toString should include(s"$name=255") // e.g. java.awt.Color[r=0,g=255,b=0]
}
}
// A bit more concise with a case statement (don't need explicit types)
property("colours2") {
forAll(colours) { case (name, colour) => colour.toString should include(s"$name=255") } // e.g. java.awt.Color[r=0,g=255,b=0]
}
// generator-driven, but no surrounding property()
// This will run, but doesn't show up in the results, which is confusing - avoid!
forAll { (n: Int) =>
whenever(n > 1) { n / 2 should be > 0 }
}
// whenever() is recommended even if generator only creates valid values - see the docs:
/* "Note that even if you are 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.)" */
property("make mine a half") {
forAll { (n: Int) =>
whenever(n > 1) { n / 2 should be > 0 }
}
}
// Free-form test, no scalacheck magic at all, using matcher
property("matcher only") {
1 shouldEqual 1
// See options for equality matching:
// http://www.scalatest.org/user_guide/using_matchers#checkingEqualityWithMatchers
}
// Free-form test, no scalacheck magic at all, using assertion
property("assert something") {
assert(Set.empty.size === 0)
}
// Do NOT do this - it has no effect (will always pass). Gives a compiler warning if you have the appropriate settings.
property("boolean only - broken!") {
1 == 0
}
// Explicitly defined generators:
import org.scalacheck.Gen
val die = Gen.choose(1, 6)
property("outputPrecision") {
forAll(die)(_.toString.length == 1)
}
property("sum") {
forAll(die, die) { case (a, b) => a + b >= 2 && a + b <= 12 }
}
// case class trick - generate random instances if there are implicit generators
// for the constructor parameters:
case class Person(name: String, age: Int)
val people = Gen.resultOf(Person) // generator of people
// though ages may be negative, so need a whenever filter or
// similar.
def doSomething(p: Person) = true // always OK, just an example
property("all the people") {
forAll(people) { p => whenever(p.age >= 0) { doSomething(p) } }
}
// Can use any function in resultOf:
val another = Gen.resultOf((x: Int) => x + 1)
}
/**
* This is what ScalaTest calls the ScalaCheck style of property-driven test.
* See http://www.scalatest.org/user_guide/writing_scalacheck_style_properties#selectingAStyle
*
*/
class Example2 extends PropSpec with Matchers with Checkers {
// No "forAll" needed here, though other examples use it
check((a: List[Int], b: List[Int]) => a.size + b.size == (a ::: b).size)
import org.scalacheck.Prop._ // needed for ==> etc
check { (n: Int) => n > 1 ==> n / 2 > 0 }
// Using ==> rather than "whenever" which is used in the ScalaTest style
}
// Funsuite example from http://stackoverflow.com/questions/21413782/sbt-doesnt-recognize-scalatest-table-driven-property-checks-as-tests
// (by Bill Venners himself)
class MySpec extends FunSuite with PropertyChecks {
test("give the test a name here") {
forAll { (x: Int, y: Int) => assert(true) }
}
}
/**
* flatspec.
* Names are mangled because duplicate test names give an error!
* and some of the names are nonsense because it's the code I'm trying out
*/
class SetSpec extends FlatSpec with Matchers with PropertyChecks {
//behavior of "An empty Set"
"2) An empty Set" should "have size 0" in {
assert(Set.empty.size === 0)
}
it should "also have size 0" in {
assert(Set.empty.size === 0)
}
val examples =
Table(
"set",
BitSet.empty,
HashSet.empty[Int],
TreeSet.empty[Int])
// table-driven
it should "again have size 0" in {
forAll(examples) { set => set.size should be(0) }
}
// generator-driven
"Number" should "divide by half" in {
forAll { (n: Int) =>
whenever(n > 1) { n / 2 should be > 0 }
}
}
}
@asarkar
Copy link

asarkar commented Nov 4, 2018

@davidallsopp How to limit the number of properties generated? For instance, if I want to stop the test after having generated 10 values.

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