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

Random Data Generation with Scalacheck -- London Scala User Group 2017

Random Data Generation with Scalacheck -- London Scala User Group 2017

Daniela Sfregola

January 17, 2017
Tweet

More Decks by Daniela Sfregola

Other Decks in Technology

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. PROPERTY BASED TESTING - PROS > Test data is less

    biased > On failing, counter-example provided > Higher confidence that our code probably works
  3. PROPERTY BASED TESTING - CONS > Restructuring your tests as

    properties is not always immediate > Not always applicable with side effects
  4. RANDOM DATA GENERATOR case class Example(text: String, n: Int) val

    example: Example = random[Example] // Example(ਈ⼝ꏣᰣ∯෢ꪔ䃂ᅟ䑪⡨⿽ᵅ䎎ߐ, 73967257)
  5. RANDOM DATA GENERATOR case class User(name: String, surname: String) "create

    a user" { val user = random[User] Post("/users", user) ~> check { status === StatusCodes.Created assertCreation(user) } }
  6. 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
  7. LESS BIASED TEST DATA For every session different test data

    will be randomly* selected * We can still fix the seed when needed!
  8. 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) } }
  9. 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) } }
  10. 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
  11. import org.scalacheck._ trait RandomDataGenerator extends ShapelessLike { private val seed

    = RandomDataGenerator.seed def random[T](implicit arb: Arbitrary[T]): T = { val gen = arb.arbitrary val optT = gen.apply(Gen.Parameters.default, seed) optT.get } }
  12. 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
  13. CUSTOMISE YOUR ARBITRARY Before: random[String] // ᭞❱᭟ⳘԺ〈ᦙ᠓ꍊꎼꙐႀ⤌惲 After: /** Generates

    a string of alpha characters */ implicit val arbitraryString: Arbitrary[String] = Arbitrary(Gen.alphaStr) random[String] // hqtbonxacrmvmuMpofwtasrojjnycwuoTfkrhOpli
  14. 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)
  15. WRAP UP > A compromise between using ScalaCheck and predefined

    fixtures > Customise the data generation to your context > Do not ignore random test failures