Test Driven Development

Test Driven Development typically means when the test code is written first. This can be a lot of fun but doesn’t fit every scenario – when the code you are writing may not be part of the finished product, time spent writing tests can be a gamble.

A perfect fit for Test Driven Development is when writing functions with a few different paths through the code: there are a certain number of key paths through the code and these can be written out as tests, ready to be run with each modification to the code.

Let’s see how an example might work:

Imagine an online store. We have a need for a method to choose where a user will be redirected based on his login attempt but the logic is complex. If his login is unsuccessful he will go to ‘bad-login’, for successful logins he will go to ‘good-login’ unless he has items in his basket. If this is the case he will go to ‘basket’… unless he has 1 or more notifications, in which case he will go to ‘notifications.’ The method will take a UserDto and return a String.

We can write these requirements as tests and make sure that, however complex, we can cover our key scenarios and run through them in less than a second by running our test class. In this example I’ll use Scala to help anyone looking to see the direction in which Java is heading. I’ll use JUnit for the tests as Scala-Java integration is fantastic and lets a Java developer keep a lot of their favourite Java frameworks while moving to Scala.

package vff.blog
import junit.framework.TestCase
import org.junit.Assert
case class UserDto(username: String, hasNotifications: Boolean, hasItemsInBasket: Boolean) {
  // line above declares the class and constructor together - each will be immutable.
}

object RedirectService {
  // object as opposed to class creates a singleton
  val LOGIN_UNSUCCESSFUL = "bad-login"
  val LOGIN_SUCCESSFUL = "good-login"
  val SHOW_BASKET = "basket"
  def redirect(userDto: UserDto, loginSuccessful: Boolean): String = {
     "" // return an empty string
  }
}

This is my basic UserDto class holding a String and 2 Booleans and my RedirectService, a singleton (note use of the word object, not class) with a function taking a UserDto and a Boolean and returning a String. For now it returns an empty String – just enough to compile and run. For our first test we will create a ‘happy path’ test – I’d say the simplest path (not necessarily ‘happy’) is login unsuccessful (nothing else matters, return a string) so we’ll code that first.

package vff.blog

import org.junit.Assert
import junit.framework.TestCase
class RedirectServiceTest extends TestCase {
  def testLoginUnsuccessful {
    val userDto = new UserDto("vincefleetwood", false, false)
    val result = RedirectService.redirect(userDto, false)
    Assert.assertEquals(RedirectService.LOGIN_UNSUCCESSFUL, result)
  }
}

When we run this test we get a failure (as expected). Time to write some code to make our test pass. We change our method as shown:

def redirect(userDto: UserDto, loginSuccessful: Boolean): String = {
  LOGIN_UNSUCCESSFUL
}

Now our method always returns ‘bad-login’. Well, it’s a start 🙂 We can now run our test and it will pass. This might not feel like a big step but we’ll get there. Time for a second test, that when the user logs in without notifications or basket items they go to ‘good-login’:

Lets add the test first:

def</strong> testLoginSuccessfulBasic {
    <strong>val</strong> userDto = new UserDto("vincefleetwood", false, false)
    <strong>val</strong> result = RedirectService.redirect(userDto, true)
    Assert.assertEquals(RedirectService.LOGIN_SUCCESSFUL, result)
  }

We add the new constant to the Redirect Service (match is like a switch that can run on any value) and our test runs but fails.

We now conditionally return the new value:

  def redirect(userDto: UserDto, loginSuccessful: Boolean): String = {
    loginSuccessful  match {
      case true => LOGIN_SUCCESSFUL
      case false => LOGIN_UNSUCCESSFUL
    }
  }

We now write the test for items in the basket:

def testLoginSuccessfulItemsInBasket {
    val userDto = new UserDto("vincefleetwood", false, hasItemsInBasket = true)
    val result = RedirectService.redirect(userDto, true)
    Assert.assertEquals(RedirectService.SHOW_BASKET, result)
}

We also write the code to pass the test – refactoring a little what we have already written but with tests to make sure it still works:

object RedirectService { // object as opposed to class creates a singleton
  val LOGIN_UNSUCCESSFUL = "bad-login"
  val LOGIN_SUCCESSFUL = "good-login"
  val SHOW_BASKET = "basket"
  def redirect(userDto: UserDto, loginSuccessful: Boolean): String = {
    loginSuccessful match {
      case true if (userDto.hasItemsInBasket) => SHOW_BASKET
      case true                               => LOGIN_SUCCESSFUL
      case false                              => LOGIN_UNSUCCESSFUL
    }
  }
}

I’m guessing this is enough for now and leave the final test as an exercise for the reader. Please leave any comments/questions.

Advertisements

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