When I first looked at Unit Testing, I did what most developers have done?ignored it. Then years ago one day when I was working on a critical piece of code, I decided to try it and realized its benefit. Now I unit test most of my code I write for commercial projects.
Testing and feedback is important, but it's critical when working with a language that's evolving. So, when I started working on "Programming Groovy: Dynamic Productivity for the Java Developer," I knew I'll face two challenges.
The first challenge is the features of the language and tools may change?I'll have to keep the book updated as the book and the language evolve.
The second challenge is, the bug fixes in the language may affect my examples.
If a feature change or bug fix affected an example or a concept explained in the book, I need to know that quickly. I can't afford to manually run each line of code and verify each page of text.
At the same time, I have to keep the code examples isolated, in separate files. In the name of automated testing, I can't compromise the clarify and isolation of my examples.
As I started writing the first few examples in the book, I was tossing around a few
ideas, wondering how to proceed. That's when I saw an email from Erik
Hatcher in a list. In his email, he mentioned that the central application that's
part of the Lucene in Action book has
strong unit tests. That inspired me to figure out a way to automate the test of
I have several examples (over 165 examples on last count) that output to the console. It took me a couple of hours to think though and figure out ways to mock all of that so the code can stand along and I can still have the output (and intended behavior) tested.
Let's look at an example. In this example, I illustrate method interception using GroovyInterceptable (there are a lots of other ways to do this in Groovy).
//InterceptExample.groovy
class Car implements GroovyInterceptable
{
def start()
{
System.out.println "start called..."
}
def invokeMethod(String name, args)
{
System.out.println("Call to ${name} intercepted... ")
def validMethod = Car.metaClass.getMetaMethod(name, args)
if (validMethod != null)
{
validMethod.invoke(this, args)
}
else
{
return Car.metaClass.invokeMethod(this, name, args)
}
}
}
def car = new Car()
car.start()
When you run InterceptExample.groovy, you will get the output:
Call to start intercepted...
start called...
Now, when I upgrade Groovy to the latest version (I have upgraded it to several snapshot versions and three release versions as I'm writing the book) I need to know if that example still works like I expected. So, I have a test for it:
//MetaProgrammingTests.groovy
class MetaProgrammingTests extends TestBase
{
void testInterceptExample()
{
evaluateScript('InterceptExample.groovy')
assertResultEquals "Call to start intercepted... start called..."
}
}
I have a number of tests related to metaprogramming and they will go into MetaProgrammingTests. The test for InterceptExamle.groovy is shown above. TestBase mocks out several things that would appear in my examples. A trimmed down version of TestBase.groovy, enough to make the above test work, is shown below:
//TestBase.groovy
class TestBase extends GroovyTestCase
{
def result = ''
def printMock = { value -> result += value }
void mockPrintln()
{
PrintStream.metaClass.println = {String value ->
printMock(value)
}
}
{ mockPrintln() }
def evaluateScript(scriptFileName)
{
def shell = new GroovyShell()
shell.evaluate(new File(scriptFileName))
}
def assertResultEquals(expected)
{
assertEquals expected, result
}
}
In my TestBase.groovy I have a lot more stuff right now---they were added as I created more examples.
It took me a couple of hours to get things figured out initially, and as I write examples, just a little time. I have a script that runs all the tests in all my example directories and reports any failures.
The effort has paid off already. Here's an excerpt from the "Unit Testing and Mocking" chapter (included in the Beta released today) from the book:
"It took me a couple of hours to figure out how to get going. Whenever I write a new example, I spend about two minutes or less to get the test written for that example. That effort and time paid off within the first few days and a few times since. So far about five examples failed as I upgraded Groovy. More important, these tests gave me assurance that the other examples are working fine and are valid."These tests helped in at least four ways:
- It helped further my understanding of Groovy features.
- It helped raise questions in the Groovy users' mailing list that helped fix a few Groovy bugs.
- It helped find and fix an inconsistency in Groovy documentation.
- It continues to help me assure that all my examples are valid and working well with the most recent version of Groovy."
==end of excerpt==
Unit testing is essential in a dynamic language like Groovy. At the same time, Groovy also makes it easier to write tests and mocks. I am glad I took the time to write these tests. If you are still on the fence about automated tests, I hope you give it a fair short and see the benefits for yourself on your projects. The tests don't have to be perfect?they only have to serve your need.