Some weeks ago I wrote about getting started with testing with scala. Now its time to explore a little more what's in that ScalaTest library.
I described one way of testing with scalatest using FeatureSpec. But there are many ways in which you can write tests. So just for the fun of it I'm using the FunSuite today:
import org.scalatest.FunSuite
import org.scalatest.matchers.ShouldMatchersclass SomeTest extends FunSuite with ShouldMatchers{
test("Some Test"){
"test" should have length (4)
}
test("Some other Test"){
"test".reverse should be ("tset")
}
}
This looks similar easy enough for every Java developer to to understand what is going to be tested. It might not be so obvious, what actually is going on in this class. Lets ignore the actual assertions for today. Note that we don't define anything but the test class itself. So the the individual tests aren't method definitions as in JUnit (and TestNG AFAIK), but calls to a function called test. It takes two arguments: the name of the test (which has to be unique in the test class) and the actual test which is just a code block. (Actually you can have additional tags after the test name, but again thats for a different article. The method test registers the test for execution. This happens at instantiation of the test class, since the calls to the test method are placed directly in the body of the test class. Once all the test classes are instantiated (and therefore all tests registered) scalatest starts executing all the tests, i.e. it executes all the code blocks passed as parameters to test.
As you might have noted, there was no 'reflection' or 'byte code magic' involved in all this. It's all straight scala code. I guess Bill Venners liked that when he was writing the code. But why should we as a user care? Because we can do all the things we can do with normal code!
Lets assume you wrote a method to check if a string is a palindrome obviously you want to have some tests for that:
class Palindrome extends FunSuite with ShouldMatchers{
def palindrome(s: String) = s == s.reversetest("empty String is a palindrome"){
assert (palindrome(""))
}test("single character is a palindrome"){
assert(palindrome("a"))
}test("sator square is a palindrome"){
assert(palindrome("sator arepo tenet opera rotas"))
}test("xyz is not a palindrome"){
assert(!palindrome("xyz"))
}
}
While the tests in this example are really simple we still see the code duplication. And once the tests get just a little more involved, it gets really annoying. But since tests are just method calls we can rewrite like this:
class Palindrom2 extends FunSuite with ShouldMatchers {
def palindrome(s: String) = s == s.reverseval testvalues = Map(true -> List("", "a", "sator arepo tenet opera rotas"),
false -> List("xyz"))for ((r, probes) ↠testvalues; probe ↠probes) {
test(probe + " is " + (if (!r) "not ") + " a palindrome") {
assert(palindrome(probe) === r)
}
}
}
I start by creating a Map from the expected result of the method palindrome to a list of test values which should yield that result. I then loop through that Map and the contained list and for each entry I call test, with a dynamic name and a fixed code block.
To do the same thing in JUnit or TestNG you have to use special features of the test frameworks. With scalatest its just a basic application of language features. I like that.
Talks
Wan't to meet me in person to tell me how stupid I am? You can find me at the following events: