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

#1 Scala Central - Random Data Generation with ScalaCheck

#1 Scala Central - Random Data Generation with ScalaCheck

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 stop 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. Finally, we will show how random data generation can also be used in development to restore our data-driven-application in a particular state.

Daniela Sfregola

September 01, 2016
Tweet

More Decks by Daniela Sfregola

Other Decks in Programming

Transcript

  1. ScalaCheck "[...] automated 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: "" from https://www.scalacheck.org
  2. Property-based testing Pros • Test data is less biased •

    When test failing, counter-example provided • Higher confidence that our code *works*
  3. Property-based testing Cons • Not always applicable when side effects

    are involved! e.g.: data-driven application and BDD (Behaviour Driven Development)
  4. Random Test Data with ScalaCheck Re-use some of ScalaCheck code

    to generate some random instances as our test data
  5. Random Test Data Pros • Less biased test data •

    Higher confidence that our code *works* • Improved test data readability • No more boring trait of fixtures to maintain!
  6. Random Test Data - Pros (2) "when creating a user"

    should { "reject creation of an underage user" in { val user = random[User].copy(age = 17) Post("/users", user) ~> check { status === StatusCodes.BadRequest assertNotCreated(user) }}} "when creating a user" should { "reject creation of an underage user" in { val user = User(name = "whatever", age = 17, address = ..., ..., email = "[email protected]") Post("/users", user) ~> check { status === StatusCodes.BadRequest assertNotCreated(user) }}}
  7. Arbitrary[T] package org.scalacheck sealed abstract class Arbitrary[T] { val arbitrary:

    Gen[T] } [...] implicit lazy val arbInt: Arbitrary[Int] = ... implicit lazy val arbDouble: Arbitrary[Double] = ... implicit lazy val arbString: Arbitrary[String] = ... implicit lazy val arbDate: Arbitrary[Date] = ... [...] implicit def arbOption[T]( implicit a: Arbitrary[T]): Arbitrary[Option[T]] = ...
  8. Gen[T] package org.scalacheck sealed abstract class Gen[+T] { def map[U](f:

    T => U): Gen[U] = ... def flatMap[U](f: T => Gen[U]): Gen[U] = ... def sample: Option[T] = ... implicit def const[T](x: T): Gen[T] = ... def choose[T](min: T, max: T)(implicit c: Choose[T]): Gen[T] = ... def oneOf[T](xs: Seq[T]): Gen[T] = ... def option[T](g: Gen[T]): Gen[Option[T]] = ... def listOf[T](g: => Gen[T]): Gen[List[T]] = ... def listOfN[T](n: Int, g: Gen[T]) Gen[List[T]] = ... [...]
  9. Gen[T] (2) //// Character Generators //// def numChar: Gen[Char] =

    ... def alphaUpperChar: Gen[Char] = ... def alphaLowerChar: Gen[Char] = ... //// String Generators //// def identifier: Gen[String] = ... def alphaStr: Gen[String] = ... def numStr: Gen[String] = ... //// Number Generators //// [...] //// Misc Generators //// lazy val uuid: Gen[java.util.UUID] = ... lazy val calendar: Gen[java.util.Calendar] = ... }
  10. random[T] import org.scalacheck._ def random[T](implicit arb: Arbitrary[T]): T = {

    val gen: Gen[T] = arb.arbitrary val sample: Option[T] = gen.sample sample.get }
  11. random[T] (2) import org.scalacheck._ implicit class RichArbitrary[T](arb: Arbitrary[T]) { def

    instance: T = arb.arbitrary.sample.get } def random[T](implicit arb: Arbitrary[T]): T = arb.instance
  12. scalacheck-shapeless case class Example(a: String) random[Example] // could not find

    implicit value for parameter // arb: org.scalacheck.Arbitrary[Example]
  13. Global Arbitrary[T] case class Example(a: String) import org.scalacheck.Shapeless._ implicit val

    arbitraryString: Arbitrary[String] = Arbitrary(Gen.alphaStr) random[Example] // Example(hqtbonxacrmvmuMpofwtasrojjnycwuoTfkrhOpli) // def alphaStr: Gen[String] = // listOf(alphaChar).map(_.mkString) // .suchThat(_.forall(_.isLetter))
  14. 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
  15. Arbitrary of custom case class 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)
  16. Other uses: Seeds • Data Driven Applications • Test/Development environment

    only! • Highly configurable • Easy to scale • Easy to maintain
  17. Thank you! • All the code is on GitHub! github.com/DanielaSfregola/random-data-generator

    • Twitter: @DanielaSfregola • My blog: danielasfregola.com