Monday 2 March 2009

Using Groovy and GUnit to test Java 'privately'

Normally if I am writing a class and find I need access to a private field in order to test correctly then I take it as a smell - something not quite right with the design. But every now and then I fail to find a way to change the design without corrupting its intent.

Now I know you can use reflection in Java to access private fields, but its a bit clunky. But recently I have been reading about Groovy, and found that it gives you full access to all your Java objects, but allows you to ignore private specifiers.
So as an example, say I have a counter class, one which allows users to get the next value of the counter, but cannot provide anyway of altering the counter for that would invalidate its purpose.

package net.usersource.example;

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
   private final AtomicInteger counter;

   public Counter() {
       counter = new AtomicInteger(0);
   }

   public int getNext() {
       return ensuringValueDoesNotRollIntoNegativeValues(counter.incrementAndGet());
   }

   private int ensuringValueDoesNotRollIntoNegativeValues(int value) {
       if( value < 0 ) {
            if( counter.compareAndSet(value, 0) ) {
                return 0;
            }
            else {
                return getNext();
            }
        }
        return value;
    }
}
Now, how do I test this without calling getNext() until it reaches Integer.MAX_VALUE? Do you know just how big MAX_VALUE really is, and how long that will take? Thats not really an option - so I need somehow to change the underlying counter.

Now I could do that with reflection, but I can do it much simpler using Groovy (which if you look closely, especially the way I have written it, looks like java) ...

package net.usersource.example

import org.junit.Test;
import org.junit.Assert;
import java.lang.Integer;

public class CounterTest {
    @Test
    public void verifyFirstObtainedCounterValueIsOne() {
        Counter c = new Counter();
        Assert.assertEquals( c.getNext(), 1 );
    }

    @Test
    public void verifyThatAtMaxIntTheNextValueIsZero() {
        Counter c = new Counter();
        c.counter.set(Integer.MAX_VALUE-1);
        Assert.assertEquals( Integer.MAX_VALUE, c.getNext() );
        Assert.assertEquals( 0, c.getNext() );
    }
}
This was TDD'd so I only knew the solution thanks to the test.

Unfortunately I have found some issues with the Eclipse Groovy Plugin - its auto-completion is a bit zealous in someways (wants to try and match everything bringing your machine to a crawl) and ignorant in others (not matching in imports). Also the GUnit results were reporting exceptions the line above the line with the issue - which caused me some confusion initially, and I need to investigate that further.

But to me this is a neat way of testing Java code, by stepping out of Java to something that makes it easier, whilst staying on the JVM.

No comments: