ATDD, Generating random tests with ScalaCheck


Further, in our series on Acceptance Test Driven Development, we would look at generating random test values with the ScalaCheck framework. In our last post we looked at, how we make our test logic respect the DRY principle by defining the values in a table. However, in many situations, you would like to generate the values dynamically so that we do not have to fill a lot of values in the table. Let us see how this is done with the help of a library called ScalaCheck

We would change our test case a bit for dynamic value generation. Look at the code sample below

class MultiplicationSpecWithGenerators extends FeatureSpec with GivenWhenThen with GeneratorDrivenPropertyChecks 

The important thing to note here is the trait called GeneratorDrivenPropertyChecks. This trait facilitates property checks against generated data using ScalaCheck.

In order to include ScalaCheck library in your project, we would need to include the following dependency

libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.9" % "test"

Let us look at the complete code sample

import org.scalatest.FeatureSpec
import org.scalatest.GivenWhenThen
import org.scalatest.prop.TableDrivenPropertyChecks
import org.scalatest.prop.GeneratorDrivenPropertyChecks
import org.scalacheck.Gen
import scala.compat.Platform

class MultiplicationSpecWithGenerators extends FeatureSpec with GivenWhenThen with GeneratorDrivenPropertyChecks {

  val validNumberOnes = for (n <- Gen.choose(-10, 300)) yield n
  val validNumberTwos = for (n <- Gen.choose(10, 15)) yield n
  
  feature("Multiplication") {
    info(" In order to avoid making mistakes")
    info("As an accountant")
    info(" I want to multiply numbers")

    scenario("Multiply two variables") {
      forAll(validNumberOnes, validNumberTwos, minSuccessful(500), maxDiscarded(30), workers(4)) {
        (x: Int, y: Int) =>
          given("a variable x with value " + x)
          and("a variable y with value " + y)

          when("i multiply " + x +" and " + y)
          val expectedResult = (x * y)
          assert((new ActualBusinessImplementation).actualMultiply(x, y) === expectedResult.toInt)
          then("i get " + expectedResult)
      }
    }

  }
}

/**
 * Actual Software Under Test
 */
class ActualBusinessImplementation{
  def actualMultiply(a:Int, b:Int) = a * b
}

As you would notice, most of the things are quite the same as in our last example where we were using TableDrivenPropertyChecks. The difference being that now, instead of using Table driven values, we end up Generating values.

  val validNumberOnes = for (n <- Gen.choose(-10, 300)) yield n
  val validNumberTwos = for (n <- Gen.choose(10, 15)) yield n

This code generates numbers between the given range. So it is -10 to 300 for validNumberOnes and between 10 to 15 for validNumberTwos.

forAll(validNumberOnes, validNumberTwos, minSuccessful(500), maxDiscarded(30), workers(4)) {
        (x: Int, y: Int) =>

Trait GeneratorDrivenPropertyChecks contains forAll method that provides various ways to check properties using generated data. Here, we are not only getting values of valiNumberOnes and validNumberTwos but also specifying the configuration parameters like minSuccessful, maxDiscarded and workers

minSuccessful 	500 	the minimum number of successful property evaluations required for the property to pass
maxDiscarded 	30 	the maximum number of discarded property evaluations allowed during a property check 
workers 	4 	specifies the number of worker threads to use during property evaluation 

Notice that the software under test in this case is the ActualBusinessImplementation class.

When we execute it, the system runs 500 successful tests with various value combinations and hence we know that the result is correct. The output of the evaluation would look something like this.

[info]     Given a variable x with value 160 
[info]     Given a variable x with value 236 
[info]     And a variable y with value 12 
[info]     And a variable y with value 12 
[info]     When i multiply 160 and 12 
[info]     When i multiply 236 and 12 
[info]     Then i get 1920 
[info]     Then i get 560 
[info]     And a variable y with value 13 
[info]     Given a variable x with value 62 
[info]     When i multiply 23 and 13 
[info]     And a variable y with value 15 
[info]     When i multiply 62 and 15 
[info]     Given a variable x with value 149 
[info]     Then i get 930 
[info]     And a variable y with value 13 
[info]     Given a variable x with value 29 
[info]     When i multiply 149 and 13 
[info]     And a variable y with value 15 
[info]     Then i get 1937 
[info]     When i multiply 29 and 15 
[info]     Given a variable x with value 105 
[info]     Then i get 435 
...
...

The ordering of the output looks a little weird because we are running the evaluation with four threads. As always the code is present on the Knoldus Github account.

Hence, as we saw it is easy to generate values with ScalaCheck and pass it to our test case. We can also specify the minimum number of generated value test which would need to be performed before we say that our code is good. In a follow up post to this ATDD series, we would look at another interesting candidate which would make life easy for stakeholders and developers. Till then.

About these ads

About Vikas Hazrati

Vikas is the CTO @ Knoldus which is a group of software industry veterans who have joined hands to add value to the art of software development. We do niche product and project development on Scala and Java. We consult and coach on effective software development and agile practices. With our focus on software craftsmanship you can be assured of a good quality at the right price. To know more, send a mail to info@knoldus.com or visit www.knoldus.com
This entry was posted in Agile, Architecture, Scala and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s