Monday, July 26, 2010

ScalaTest, A better way to test.

ScalaTest is a flexible testing framework that allows you to test the way you feel most comfortable testing. For example, it allows you to write tests in a functional manner or in a behavior driven manner. It allows you to write tests for both Scala and Java. It even has traits that can be pulled in to enhance your existing scala JUnit or TestNG tests if you prefer. I have been writing many of my scala tests using both TestNG and JUnit and recently introduced ScalaTest matcher traits into my existing tests. I've come to find that tests as they get more complex tend to get more difficult to read. The ScalaTest matchers go a long way in making your tests easier to read and therefore easier to maintain. This is important when practicing TDD.

Lets start with the following example using the basic JUnit assertions.

package com.foo

import org.junit.Test
import scala.collection.mutable.Map

class FooTest{
val company = Company

@Test
def addsCorrectly {
val jSmith = Person("John", "Smith", 1)
company.hire(jSmith)
assert(company.employeeCount == 1)
assert(company.employees.contains(jSmith))
}
}

object Company {
private val _employees = Map[Long, Person]()
def hire(user:Person) = _employees + (user.id -> user)
def employeeCount = _employees.size
def employees = _employees.values
}

case class Person(firstName:String, lastName:String, id:Long)

As you can see, the JUnit assertions do the job but they just don't read well. It's import to make any code, even test code, easy for anyone else to jump in and quickly understand. The easer code is to read, the easier it is to maintain.

The Hamcrest matcher library provides a variety of matchers that not only provide good functionality but make code a little nicer to read. Lets revisit the example above and rewrite the test method using the Hamcrest matcher library.


package com.foo
import org.junit.Test
import org.hamcrest.Matchers._
import org.hamcrest.MatcherAssert._

class FooTest{
val company = Company

@Test
def addsCorrectly {
val jSmith = Person("John", "Smith", 1)
company.hire(jSmith)
assertThat(company.employeeCount, is(1))
assertThat(company.employees(1), is(jSmith))
}
}

Although the Hamcrest matcher library provides a wide variety of matcher functionality, it still doesn't read as nicely as it could. Lets try this again, this time using the ScalaTest matchers.


package com.foo

import org.scalatest.matchers.ShouldMatchers
import org.scalatest.matchers._
import org.junit.Test

class FooTest extends ShouldMatchers {
val company = Company

@Test
def addsCorrectly {
val jSmith = Person("John", "Smith", 1L)
company.hire(jSmith)

company.employeeCount should be (1)
company.employees should contain value (jSmith)
}
}


There, now that reads nicely. To use the matchers you can simply add the trait to your existing class and you're ready to go. The full source for this example can be found at github.

Saturday, July 24, 2010

Scala Testing with Junit 4 and Annotations

JUnit annotations can easilty be used in Scala based tests just as they can in Java based tests.

The following is a basic scala test using the Junit annotations. For the sake of keeping the code as simple as possible we won't introduce thread safety into the example.


package com.foo
import org.junit.{Before, Test}
import org.junit.Assert.assertTrue

class BasicTest{
var bankAccount:BankAccount = _

@Before
def setup() = {bankAccount = new BankAccount(100)}

@Test
def addsCorrectly {
assertTrue(bankAccount.add(150) == 250)
}
}

class BankAccount(private var balance:Double){
def add(amt:Double):Double = { balance + amt }
}

As you can see, annotating JUnit tests in scala is pretty much the same as it is in java.
Except for some syntactical differences, handling tests that expect exceptions is also very similar to the way they are handled in java unit tests. Consider the following example:


package com.foo
import org.junit.Test
class FooTest{
@Test { val expected = classOf[ IllegalArgumentException] }
def shouldThrowException {
Foo.methodThatThrowsException
}
object Foo{
def methodThatThrowsException() = throw new IllegalArgumentException
}
}

One thing you may have noticed is that I declared a val in the annotation. Scala processes annotations in a different manner than Java. More on this can be found here.