Scalacheck

Today was Beat Technology’s Hackday!

I wanted to explore and play with property based testing via Scalacheck. I have used quickcheck in Haskell before but only as a student currently I find it difficult to translate the little I know about property based to something that can be used for work since I mostly use Scala at work. My goal for today is to write some scalacheck-tests against some production code at work.

For inspiration as to where to begin with Scalacheck I started by watching a Scala World talk about property based testing “Introduction to ScalaCheck—Noel Markham”

Inspired by the talk I found a piece of production code that I could play a bit with writing tests for:

final case class Price(currency: Currency, amount: Long)

/** Parses a price amount string into a Price object with the given currency.
  * Two decimal points are targeted, and any smaller points are discarded.
  * So a price of "10.7712" will not be subjected to arithmetic rounding, but
  * simply be chopped into 10.77.
  */
def parsePrice(
    amount: String,
    currency: Currency
): Price { /* Implementation is snipped */ }

In the above example I snipped the implementation but this is reflective of how I read the code at the time also (I did not :)) because the scaladoc was enough to describe what the piece of code did and I set out to test that what the scaladoc said the code did was correct.

Generators

Scalacheck provides some default generators for basic types. However since we have an amount String and Currency type here I opted to create my own generators like so:

def amountString: Gen[String] = for {
  a <- Gen.nonEmptyListOf(Gen.numChar)
  b <- Gen.nonEmptyListOf(Gen.numChar)
} yield (a ++ List('.') ++ b).mkString

def currency: Gen[Currency] =
  Gen.oneOf(Currency.getAvailableCurrencies().asScala)

With these generators I was able to generate “random” values:

@ amountString.sample
res59: Option[String] = Some("014529498608114016731559.271805319988809046340042341")

@ currency.sample
res60: Option[Currency] = Some(RSD)

Writing a property test

With the generators in place I could then write my first scalacheck tests. First I wanted to check that given the generators the function does not throw.

// for all values generated by generators (by default 100 `samples` are generated)
// check that the following holds
forAll(amountString, currency) { (a, c) =>
  Either.catchNonFatal(parsePrice(a, c)).isRight
}.check()

+ OK, passed 100 tests.

It worked :)

I then wrote a test to check that the scaladoc said was correct and that the first two digits after . in the amount string would be the last two digits in the Price.amount

forAll(amountString, currency) { (amount, currency) =>
  val firstTwoOriginalDecmals = amount.split('.').last.take(2).toLong
  val firstTwoParsedDecmals = (parsePrice(amount, currency).amount % 100)

  firstTwoParsedDecmals == firstTwoOriginalDecmals
}.check()

+ OK, passed 100 tests.

Success!


Author | Thomas Bjertnes

Developer.