Testing Groovy Scripts

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.

About Ken Kousen
I teach software development training courses. I specialize in all areas of Java and XML, from EJB3 to web services to open source projects like Spring, Hibernate, Groovy, and Grails. Find me on Google+ I am the author of "Making Java Groovy", a Java / Groovy integration book published by Manning in the Fall of 2013, and "Gradle Recipes for Android", published by O'Reilly in 2015.

8 Responses to Testing Groovy Scripts

  1. Pingback: Tweets that mention Testing Groovy Scripts « Stuff I’ve learned recently… -- Topsy.com

  2. Actually, instead of redirecting System.out, you should set the “out” variable in the binding, which is used by printle. So you could avoid encoding issues, avoid redirecting streams, by doing something like this:

    def content = new StringWriter()
    binding.out = new PrintWriter(content)

    println “hello”

    assert content.toString() == “hello\n”

  3. Ken Kousen says:

    You know, that’s why I blog about these things. It’s only partly to share what I’ve learned. More importantly, it’s to give people like you an opportunity to show me what I didn’t know in the first place. 🙂

    Thanks for this. I’ll definitely revise my tests to match this approach.

  4. Bob Swift says:

    https://studio.plugins.atlassian.com/wiki/display/GINT/How+to+test+Hello+World is a simple example using GINT to test groovy scripts.

  5. Steve Olsen says:

    FYI, the “ok” is not appearing in the script_with_variable.groovy code. Actually, it looks like it shows up for a split second, and then the code formatting wipes it out so it’s all empty. At least it does in Chrome and Opera.

    Also, could you post an updated Test file, just so I’m sure I followed Laforge’s advice properly? Thanks!!

  6. Ken Kousen says:

    That’s weird about the ok vanishing in the blog post. I’ll try adding a comment and see if that helps.

    The test case at GitHub is already updated to use Guillaume’s approach. See the code at https://github.com/kousen/TestingGroovyScripts . Even if you’re not a git user, you can browse the code online. I made the changes he suggested right away. When Guillaume Laforge tells me something, I listen. 🙂

  7. Steve Olsen says:

    The ok is now ok 🙂

    I tried that StringWriter approach myself, in my hellow_world.groovy script I typed println "hello world" two times. But I wasn’t getting a good assert comparison. However, I changed the code to print "hello world\n" twice, and everything asserted perfectly.
    In other words, this assert line assertEquals response.toString().trim(), 'hello world\nhello world\n' works with two print’s in the script, but not two println’s.
    Any ideas what I’m doing wrong?

  8. Pingback: Confluence: Technology

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: