Testing Groovy Scripts - No Fluff Just Stuff

Testing Groovy Scripts

Posted by: Kenneth Kousen on February 24, 2011

I often start Groovy development with scripts rather than classes. Scripts are quick and easy, both to write and to run, and the feeling of having only a few lines of code makes me more willing to experiment. Most of my scripts eventually turn into classes, but once in a while, either for convenience or as demos, they stay the way they are.

This happens a lot while I’m working on my book*. I often generate little scripts that illustrate one point or another, and most of them aren’t worth converting into classes.

(*What book is that, you say? Why, Making Java Groovy, which shows you how to add Groovy to Java to make your development tasks easier. It’s available now from the Manning Early Access Program at http://manning.com/kousen)

While I’m not sure I practice true Test Driven Development (TDD), I know I practice GDD. That’s Guilt Driven Development, which means if I write anything significant that isn’t tested I feel guilty about it, so I then write the tests. In this post I’ll show how I now write tests for my Groovy scripts.

Before I do so, however, I should acknowledge the assistance of the indefatigable Hamlet D’Arcy on the Groovy Users email list. He blogged about a similar issue back in 2006 (!). Of course, he was dealing with straight Java back then and trying to manage standard error.

There are two features I need to use:

  • The groovy.lang.GroovyShell and groovy.lang.Binding classes, and
  • Overriding System.out to capture printed output

I use a GroovyShell to execute the scripts, and a Binding to manage input and output variables, if any. To handle printed output, I redirect standard output to a ByteArrayOutputStream and then check the results.

To illustrate, here are three extremely powerful scripts. The first is the classic “Hello, World!” script in Groovy. I stored it in a file called hello_world.groovy.

println 'Hello, World!'

Second, here is a script that contains an assert in it, in a file I called script_with_assert.groovy.

def ok = true
assert ok

I used a local variable, called ok, in that script, mostly to contrast it with a binding variable. Speaking of which, here’s my super duper script that has a binding variable in it, stored in a file called script_with_variable.groovy.

package mjg.scripts
ok

Recall from the fantastic book Groovy in Action* (even after all these years and language changes, I still find valuable information in there) that if a variable in a script is not declared, then it can be set and retrieved via an instance of groovy.lang.Binding.

 

(*Yeah, I linked to the second edition, even though I’m still using the first edition on a regular basis. That’s still my all-time favorite technical book, so I don’t mind recommending the new version.)

With all that in mind, here’s my JUnit 4 test, written in Groovy for convenience.

package mjg.scripts

import static org.junit.Assert.*

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

class ScriptTests {
    GroovyShell shell
    Binding binding
    PrintStream orig
    ByteArrayOutputStream out

    @Before
    void setUp() {
        orig = System.out
        out = new ByteArrayOutputStream()
        System.setOut(new PrintStream(out))
        binding = new Binding()
        shell = new GroovyShell(binding)
    }

    @After
    void tearDown() {
        System.setOut(orig)
    }

    @Test
    void testScriptWithAssert() {
        shell.evaluate(new File("src/mjg/scripts/script_with_assert.groovy"))
    }

    @Test
    void testScriptWithTrueVariable() {
        binding.ok = true
        shell.evaluate(new File("src/mjg/scripts/script_with_variable.groovy"))
        assertTrue shell.ok
    }

    @Test
    void testScriptWithFalseVariable() {
        binding.ok = false
        shell.evaluate(new File("src/mjg/scripts/script_with_variable.groovy"))
        assertFalse shell.ok
    }

    @Test
    void testHelloWorld() {
        shell.evaluate(new File("src/mjg/scripts/hello_world.groovy"))
        assertEquals "Hello, World!", out.toString().trim()
    }

}

Actually, all my scripts (and the test class) are in a package called mjg.scripts, where mjg stands for Making Java Groovy, of course.

 

I made the GroovyShell and the Binding instances attributes so I could reuse them. It turns out that by keeping a reference to the Binding, I can still set and get variables using it even though I’ve already instantiated by Groovy shell around it. That’s helpful.

In the set up method (annotated with @Before), I save the current output stream into an attribute so I can restore it later in the tear down method (annotated with @After). That’s not really necessary here, but it seems like a good practice in general.

I’m using a ByteArrayOutputStream to hold the printed data in memory. The System.setOut method requires a PrintStream as an argument, so I wrapped the byte stream inside one.

The first test, testScriptWithAssert, finds the proper file and executes it. That script has the line assert ok in it, after setting the local variable ok to true. If I go into the script and change ok to false, the test fails. This tells me two things:

  1. If my scripts are full of assert statements, I can run them as part of a normal build and failures will be reported in the usual way.
  2. If an assert fails, there isn’t much I can do to prepare for it in my tests. I can’t, for example, wrap the call inside an assertFalse call. The failure happens before I get back, for one thing.

I basically have to hope that any assert statements in my scripts always succeed, or my tests will fail. Arguably that’s what I want anyway.

The next two tests, testScriptWithTrueVariable and testScriptWithFalseVariable, set the ok variable from the binding, then run the script. The script in question just returns the variable. I check the binding variable at the end of each script, just to prove that I was able to set it properly with the binding.

Finally, I test the “Hello, World!” script. By redirecting standard out to the byte stream, I’m able to capture the print output in a variable. I then invoke the toString method to get the value in the buffer. The trim call is added to handle any whitespace associated with carriage returns, line feeds, etc.

In his blog post, Hamlet used an encoding as the argument to his toString call, claiming that otherwise the output would be different on different platforms. I decided to let Groovy worry about that, so hopefully I won’t get burned by it later.

That’s all there is to it. Now, whenever I add Groovy scripts to a chapter of my book, I make sure to add a test class to check them as well. So far that seems to be working just fine.

Any comments, especially about errors or omissions, are of course welcome. For those who have already purchased the book, (1) you totally rock, and (2) a new chapter will be added to the MEAP early next week, on using Groovy with SOAP-based web services. I’ll blog about that when the new chapter appears.

Note: the source code for this project, including a Gradle build file, is located in my TestingGroovyScripts repository at GitHub.


Kenneth Kousen

About Kenneth Kousen

Ken Kousen is a Java Champion, several time JavaOne Rock Star, and a Grails Rock Star. He is the author of the Pragmatic Library books “Mockito Made Clear” and “Help Your Boss Help You,” the O'Reilly books “Kotlin Cookbook”, “Modern Java Recipes”, and “Gradle Recipes for Android”, and the Manning book “Making Java Groovy”. He also has recorded over a dozen video courses for the O'Reilly Learning Platform, covering topics related to Android, Spring, Java, Groovy, Grails, and Gradle.

His academic background include BS degrees in Mechanical Engineering and Mathematics from M.I.T., an MA and Ph.D. in Aerospace Engineering from Princeton, and an MS in Computer Science from R.P.I. He is currently President of Kousen IT, Inc., based in Connecticut.

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »