Entrepreneur. Creator of Groovy++ Alex is a DZone Zone Leader and has posted 31 posts at DZone. You can read more from them at their website. View Full User Profile

GrUnit: Groovy way of unit testing

03.31.2010
| 11165 views |
  • submit to reddit
There is continuation of this article now

We, Java/Groovy developers, write unit tests all the time. I believe there is no need to convince anybody why is that important. My goal today is to show small but convinient experimental tool included in to Groovy++ (with potential to be backported in to Groovy Core). You can find more information about GrUnit and Groovy++ at Groovy++ project homepage

So what is GrUnit?

GrUnit is Groovy AST (abstract syntax tree) transformation, which allows just two things

  • writing JUnit tests a little bit less verbose
  • making tests executable as Groovy scripts

Let us start with example

@Typed package org.mbte.groovypp.compiler

import org.codehaus.groovy.tools.FileSystemCompiler
import org.codehaus.groovy.control.CompilerConfiguration

def finder = new FileNameFinder ()

String [] names = finder.getFileNames("./StdLibTest/tests/", "**/*Test.groovy")
new FileSystemCompiler (new CompilerConfiguration()).compile (names)

Looks like regular Groovy script, right? And it is regular Groovy script. But at the same time it is JUnit test case with just one test. Here is output after execution of the script. 

.
Time: 3.558

OK (1 test)

Educated reader can notice that this is normal output of junit.textui.TestRunner. That's correct. Our class extends brilliant GroovyTestCase and main method of the class simply calls test runner. Of course, body of our script becomes body of the only one test in the test case.

The only thing we need to do is to place the script in a file with .grunit extension.

How do we achive that? Magic?

Of course there is no magic involved. The tecnique we use (extremely powerful and very popular in Groovy world) is compile time AST transformation. AST transformation is possibility to extend compiler behavior with plugins, which at some stages of compilation modify interanl presentation of the code.

Transformations we do in our case

  • our script class should extend GroovyTestCase
  • main method should call junit.textui.TestRunner
  • body of the script should be transformed in to public void testXXX () method

Now let us add one more test to our script. It can be done very easy by adding testXXX {...} call at any place on top level of our script

testTestsExists {
assertTrue new File("./StdLibTest/tests/").exists()
}

def finder = new FileNameFinder ()

String [] names = finder.getFileNames("./StdLibTest/tests/", "**/*.groovy")
new FileSystemCompiler (new CompilerConfiguration()).compile (names)

And voila! Output of our script now looks differently - we have two tests running

..
Time: 3.372

OK (2 tests)

You want more? Our pleasure

testTestsExists {
assertTrue new File("./StdLibTest/tests/").exists()
}

testSuperclassName {
assertEquals "groovy.util.GroovyTestCase", this.class.superclass.name
}

def finder = new FileNameFinder ()

String [] names = finder.getFileNames("./StdLibTest/tests/", "**/*.groovy")
new FileSystemCompiler (new CompilerConfiguration()).compile (names)

Which executes already 3 tests

...
Time: 3.642

OK (3 tests)

Maybe you want to use some other base class for your test? No problem. Just add extendsTest XXX on top level of the script

extendsTest GroovyShellTestCase

testTestsExists {
assertTrue new File("./StdLibTest/tests/").exists()
}

testInstance {
assertEquals "groovy.util.GroovyShellTestCase", this.class.superclass.name
}

def finder = new FileNameFinder ()

String [] names = finder.getFileNames("./StdLibTest/tests/", "**/*.groovy")
new FileSystemCompiler (new CompilerConfiguration()).compile (names)

And of course setUp()/tearDown() are also supported. Again we just need to add it somewhere on the top level of our script. The code below is one of most important tests in Groovy++ code base - we test that we able to compile our standard library

extendsTest GroovyFileSystemCompilerTestCase

@Field FileNameFinder finder

setUp { finder = new FileNameFinder () }

tearDown { finder = null }

testFinderNotNull { assertNotNull finder }

testCompilerNotNull { assertNotNull compiler }

def names = finder.getFileNames("./StdLib/src/", "**/*.groovy")
names.addAll(finder.getFileNames("./StdLib/src/", "**/*.java"))
compiler.compile (names as String[])

But the maybe most interesting part of story is that very often you don't need setUp/tearDown. Let us rewrite test above using so called accumulated tests

extendsTest GroovyFileSystemCompilerTestCase

testCompilerNotNull { assertNotNull compiler }

def srcDir = "./StdLib/src/"
testSrcExists {
assertTrue new File(srcDir).exists()
}

def finder = new FileNameFinder()
checkNamesNonEmpty {
def names = finder.getFileNames(srcDir, "**/*.groovy")
names.addAll(finder.getFileNames(srcDir, "**/*.java"))
assertFalse names.empty
}

compiler.compile (names as String[])

Accumulated tests is a way to intermix script code with testXXX{...} tests. All script code before testXXX{} statement will be included in to the body of newly generated test method. We can also use chackXXX{...} instead of testXXX{...}. It will also lead to creation of new test method but code of closure inside checkXXX will be accumulated for use by later tests.

The main point is to have very easy way to split tests in to more granular parts for ease of debugging. Let us see what previous code snippet replace. Probably our code looks a little bit nicer

class CompileStdLibTest extends GroovyFileSystemCompilerTestCase {
void testCompilerNotNull () {
assertNotNull compiler
}

void testSrcExist () {
def srcDir = "./StdLib/src/"
assertTrue new File(srcDir).exists()
}

void testNamesNonEmpty () {
def srcDir = "./StdLib/src/"
def finder = new FileNameFinder ()

def names = finder.getFileNames(srcDir, "**/*.groovy")
names.addAll(finder.getFileNames(srcDir, "**/*.java"))

assertFalse names.empty
}

void test$main {
def srcDir = "./StdLib/src/"
def finder = new FileNameFinder ()

def names = finder.getFileNames(srcDir, "**/*.groovy")
names.addAll(finder.getFileNames(srcDir, "**/*.java"))

assertFalse names.empty

compiler.compile(names as String[])
}

static void main (String [] args) {
junit.textui.TestRunner.run(CompileStdLibTest)
}
}

At the end I want to notice that there is nothing Groovy++ specific in GrUnit (except it is written in Groovy++ and included in to Groovy++ distro), so it can be easily ported in to Groovy Core if at some point we will decide it make sense.

I hope it was not boring. Thank you for reading and till next time.
Published at DZone with permission of its author, Alex Tkachman.