Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Random Data Generation with ScalaCheck - Scala By the Bay 2016

Random Data Generation with ScalaCheck - Scala By the Bay 2016

ScalaCheck is a well-known library for property-base testing. However, property-base testing is not always possible when side effects are involved, for example when writing an integration test that involves data being stored in a database. When writing non-property-base tests, we often need to initialise some data and then verify some assertions on it. However, manual data generation can make our data biased and preventing us from spotting bugs in our code. Having our data generated randomly not only it would make our test less biased, but it will also make it a lot more readable by highlighting what part of our data are actually relevant in our test.

In this talk we will discuss how to reuse some of the existing ScalaCheck code to generate random instances of given types and how these can be combined to generate random case classes. We will analyse the properties of a ScalaCheck generator and provide examples of how we can manipulate existing generators to meet our needs.

Daniela Sfregola

November 12, 2016
Tweet

More Decks by Daniela Sfregola

Other Decks in Programming

Transcript

  1. SCALACHECK - PROPERTY BASED TESTING property("startsWith") = forAll { (a:

    String, b: String) => (a+b).startsWith(a) } // + String.startsWith: OK, passed 100 tests. property("concatenate") = forAll { (a: String, b: String) => (a+b).length > a.length && (a+b).length > b.length } // ! String.concat: Falsified after 0 passed tests. // > ARG_0: "" // > ARG_1: ""
  2. RANDOM DATA GENERATOR case class Example(text: String, n: Int) val

    example: Example = random[Example] // Example(ਈῒ!䑪⡨ᵅ䎎, 73967257)
  3. RANDOM DATA GENERATOR case class User(name: String, surname: String) "create

    a user" { val user = random[User] Post("/users", creation) ~> check { status === StatusCodes.Created assertCreation(user) } }
  4. FIX YOUR SEED Each session has a seed number associated

    Generating random data with seed -2481216758852790303 Use it to debug problematic tests export RANDOM_DATA_GENERATOR_SEED=-2481216758852790303 unset RANDOM_DATA_GENERATOR_SEED
  5. LESS BIASED TEST DATA For every session different test data

    will be randomly* selected! * We can still fix the seed when needed
  6. EASIER TO MAINTAIN case class User(name: String, surname: String, age:

    Int) "create a user" { val user = random[User] Post("/users", user) ~> check { status === Created assertCreated(user) } }
  7. IMPROVED READABILITY case class User(name: String, surname: String, age: Int)

    "reject user creation of an underage user" { val user = random[User].copy(age = 17) Post("/users", user) ~> check { status === BadRequest assertNotCreated(user) } }
  8. SCALACHECK-SHAPELESS2 AUTOMATICALLY INFERS ARBITRARY[T] IF: > T is a case

    class > T is an sealed trait ...BUT IT WILL IMPACT THE COMPILATION TIME 2 github.com/alexarchambault/scalacheck-shapeless
  9. ARBITRARY OF CUSTOM TYPE import java.util.Currency import scala.collection.JavaConversions._ implicit val

    arbitraryCurrency: Arbitrary[Currency] = Arbitrary { Gen.oneOf(Currency.getAvailableCurrencies.toSeq) } random[Currency] // java.util.Currency = OMR
  10. CUSTOMISE YOUR ARBITRARY Before: random[String] // !!ⳘԺ Ꙑ After: /**

    Generates a string of alpha characters */ implicit val arbitraryString: Arbitrary[String] = Arbitrary(Gen.alphaStr) random[String] // hqtbonxacrmvmuMpofwtasrojjnycwuoTfkrhOpli
  11. CUSTOMISE YOUR ARBITRARY case class Person(name: String, age: Int) implicit

    val arbitraryPerson: Arbitrary[Person] = Arbitrary { for { name <- Gen.oneOf("Daniela", "John", "Martin") age <- Gen.choose(0, 100) } yield Person(name, age) } random[Person] // Person(John,16)
  12. SUMMARY > A compromise between using ScalaCheck and predefined fixtures

    > Customise the data generation to your context > Do not ignore random test failures > Use it only for test purposes