Sunday, September 18, 2011

Ruling my expectations with Junit

For once no Clojure, nor Scala in this post.There is no whining in saying that as a java developer by day, I too rarely have the opportunity to learn or teach something, because of the attitude of diletante people more concerned by "making a career" than committing into their developer's job. But last friday a nice thing happened. I went lucky playing with Junit rules.

Let us settle the environment. I am working on the worst project I have ever been working on. By itself the project is very small (less than 70k lines of code).
It does clearly break all the rules of decent architecture reasoning in any way, its internals being deeply rotten by terrible missuses of the already invasive and useless Spring framework, but also tons of other under used dependencies (typically Apache open sources).
I won't debate over the use of Spring because I don't want this blog to be polluted by frameworks advocates sentences without interest. I am an old dog paid for using his brain rather than pressing dumbly on buttons like a monkey. I never had the need to use anything but standards API. I certainly do not need a jackhammer framework invading my perm gen to pass parameters to a constructor. End of debate.

As a TDD advocate, armed with Martin Fowler and Keriewsky books on refactoring, but also Michael Feathers and Meszaros writings, I patiently implement or fix the modules using Junit. Junit is indeed a (really small) framework per se, but above all Junit is a tool (which Spring is not). Junit is a carpenter chisel compared to the cited jackhammer.
One impact of the unfortunate pervasive injection is the difficulty in testing classes, sometime hosting nearly twenty injections. One other common problem dealing with legacy code, is exception testing and resources freeing. These two aspects rarely live in perfect harmony.

I chose to solve the invasive injection problem using (alas) mocks, accessible via seam points, presented by M. Feathers in his book As far as the exceptions are concerned Junit provides us with helpful classes and annotations.

Let see through an example. The following code reproduces a typical recurring pattern in the application but of course I changed names in order not to face copyright problems (who would want to copyright that anyway :)) :

public void invoke() throws GenericException{
    try {
//Do some stuff in database
       if (dbError()) {
           logSomething("dumb");
           throw new TechnicalException(0x01, "something happened");
       }
    } catch (final TechnicalException exception) {
        throw new GenericException(0x10, "someErrorMessage");
    } finally {
        freeReesources();
    }
}

This recurring pattern mixes technical exceptions with generic exceptions, expects some resources freeing, and also some logging. Of course there is also unconventional error code attached to the exceptions.
The translation from one error code to another is one of the strangest things I have ever witnessed. I cannot describe they purpose but take for granted that as a matter of fact an exception in this system is not passive and fires action on creation (yes, who can believe it) 

All the logged information must be tested, and at least the exiting exception code must also be tested before refactoring. It is vital too, to check that resources are released and messages are logged. (The content matters, guess why ;)) 

 The complete demo code I wrote for the purpose of testing, looks like:

 
public final class Controller {
    private boolean resourcesWereFreed;
    private String loggedMessage;

    public void invoke() throws GenericException{
        try {
           if (dbError()) {
               logSomething("dumb");
               throw new TechnicalException(0x01, "something happened");
           }
        } catch (final TechnicalException exception) {
            throw new GenericException(0x10, "someErrorMessage");
        } finally {
            freeReesources();
        }
    }

    private void logSomething(final String dumb) {
        loggedMessage = dumb;
    }

    private boolean dbError() {
        return true;
    }

    private void freeReesources() {
        resourcesWereFreed = true;
    }

    public boolean hasReleasedResources() {
        return resourcesWereFreed;
    }

    public String loggedMessage() {
        return loggedMessage;
    }
}


where I introduced spying flag methods to checks which other methods were called. In real life I found my self needing to create various self tests, God I hate that. The code for the exceptions :

 

public class GenericException extends Exception{
    private int code;

    public GenericException() {
        super();
    }

    public GenericException(final int codeValue, final String message) {
        super(message);
        code = codeValue;
    }

    public int getCodeErreur() {
        return code;
    }
}

public final class TechnicalException extends GenericException{
    public TechnicalException() {
        super();
    }

    public TechnicalException(final int code, final String message) {
        super(code, message);
    }

}


A first approach to challenge the exception throwing in Junit - using the @Test(expected= ...) facility -looks like:

@Test(expected = GenericException.class)
    public void doStuff_WithFailingInvocation_ShouldThrowException() throws GenericException {
        controller().invoke();
    }
This first level approach allows me to only check my exception type. Try the following:

 
@Test(expected = GenericException.class)
    public void doStuff_WithFailingInvocation_ShouldThrowException() throws GenericException {
        controller().invoke();
        assertThat(true, is(false));
        trace("I can't do anything there... :(");
    }


Surprise, test passes although the exception is being thrown. 
Naturally, the code following the challenge is never invoked. 
 We have two problems now. 

We would like to test more on the exception, that these damned resources have been released and that messages have been logged. 

 The first point can be cleared, sort of. Here come the Junit rules. I encourage you to hit Junit rules on Google and you will find deeper explanations than mine (here and there for example). Basically, Junit rules provide you with a nice way to intercept the call to your test,  using a decorating class named a Statement. The statements are ruled, meaning that a rule implementation can choose on which test to apply the statement. 
In the previous links, Dale H. Emery provides a nice detailed explanation of the the sequence of events during the execution of ruled tests. See the order of event as a kind of @InvokeAround like in J2EE 5/6. 

The Junit framework already provides default rules so to create n'delete temporary folders or -guess what - to check thrown expectations. Good. Let's try it then.


    @Rule
    public ExpectedException expected = ExpectedException .none();

    @Test
    public void doStuff_WithFailingInvocation_ShouldThrowExceptionWithCorrectMessage() throws GenericException {
        expected(GenericException.class, withErrorMessage());
        controller().invoke();
        assertThat(true, is(false));
        trace("I can't do anything there... :(");
    }

   private String withErrorMessage() {
        return "someErrorMessage";
    }

   private void expected(final Class<GenericException> genericExceptionClass, final String message) {
        expected.expect(genericExceptionClass);
        expected.expectMessage(is(equalTo(message)));
    }


I used an ExpectedException instance. At the very start I declare an empty expectation, that will be applied to all my tests. An empty expectation expects...nothing. If I do not want to challenge an exception, the behaviour is crystal clear for the test: nothing gets matched. 

 Before firing the tests per se I set my rule expectations using standard Junit matchers (such an elegant DSL...). The ExpectedException definition allows me to challenge both the class and the message content. So far so good, but an exception is still raised and no check is applied onto the system under test (SUT) afterwards. The test is still green while we willingly declared a wrong assertion. 

 Hum, what about making our own rule? 
What do we want? Some contract that would check at least that the resources are freed, something like that:

 
@Rule
public Contract contract = contract().postCheck(resourcesHaveBeenReleased());

But we should be ask for additional checking like:

withContract().postCheck(messageWasLogged());

I propose you a solution implementing the TestRule interface, the MethodRule interface being deprecated as of the 4.9 version. No copyright, till I did the job for the guys in 4.8 using MethodRule ;) :

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public final class Contract implements TestRule {
    private final List<Callable>?<> list;

    private Contract() {
        super();
        list = new LinkedList<>();
    }

    @Override
    public Statement apply(final Statement base, final Description description) {
        return contractedStatement(base);
    }

    private Statement contractedStatement(final Statement base) {
        return new ContractedStatement(base);
    }

    private void postCheck() throws Exception {
        for (final Callable<?> callable : postConditions()) {
            callable.call();
        }
    }

    public Contract postCheck(final Callable<?> callable) {
        postConditions().add(callable);
        return this;
    }

    private List<Callable<?>> postConditions() {
        assert list != null;
        return list;
    }

    public static Contract contract() {
        return new Contract();
    }

    private class ContractedStatement extends Statement {
        private final Statement base;

        public ContractedStatement(final Statement decorated) {
            base = decorated;
        }

        @Override
        public void evaluate() throws Throwable {
            try {
                base().evaluate();
            } finally {
                withContract().postCheck();
            }
        }

        private Contract withContract() {
            return Contract.this;
        }

        private Statement base() {
            return base;
        }
    }
}


The apply method takes as input parameter a statement in charge of invoking the underlaying test method. This base statement is then wrapped by a ContractedStatement that will run the action throwing a Throwable if anything goes wrong. 
The contracted statement always executes the postChecked method hosted by the Contract. In essence postCheck will execute a set of registered blocks of code to apply. 
Unfortunately this is not Scala we are working with, so the Java abstraction that looks like most to a block of code is a Callable, as being able to throw an exception if necessary, and returning some value (although we do not need this feature). 
 From the test all becomes clearer. We only have to create callables on demand, each callable wrapping the expected assertions:

 

 @Rule
   public Contract contract = contract().postCheck(resourcesHaveBeenReleased());


   @Test
    public void doStuff_WithFailingInvocation_ShouldThrowExceptionAndReleaseResources() throws GenericException {
        expected(GenericException.class, withErrorMessage());
        withContract().postCheck(messageWasLogged());
        controller().invoke();
    }


   private Callable<?> messageWasLogged() {
        return new Callable<Object>() {
            public Object call() throws Exception {
                assertThat(controller().loggedMessage(), is(equalTo("dumb")));
                trace("I am there too");
                return null;
            }
        };
    }

    private Callable<?> resourcesHaveBeenReleased() {
        return new Callable<Object>() {
            public Object call() throws Exception {
                assertThat(controller().hasReleasedResources(), is(true));
                trace("Hey I can test that now");
                return null;
            }
        };
    }


Tests are green and you can now insert a wrong assertion. It will fail. 

 My last wish was testing the code value of the raised exception. The code shape for this scenario could be very close to the code shape of the ExpectedException one:

 

 @Test
    public void doStuff_WithFailingInvocation_ShouldThrowGenericExceptionAndReleaseResources() throws GenericException {
        expected(GenericException.class, code(), withErrorMessage());
        withContract().postCheck(messageWasLogged());
        controller().invoke();
    }

    private int code() {
        return 0x10;
    }

    private Contract withContract() {
        return contract;
    }

    private void expected(final Class<GenericException> genericExceptionClass, final int code, final String message) {
        expectedGeneric.expect(is(equalTo(code)), genericExceptionClass, is(equalTo(message)));
    }

    private String withErrorMessage() {
        return "someErrorMessage";
    }

This step, I decided, to make it as a hack of the ExpectedException class creating my own ExpectedGenericException. This test helper would then be specific to the family of tests in my business layer as it targets the whole hierarchy of business/technical exceptions rooted by the same GenericException. The Rule class is the ExpectedGenericException:

 
import com.promindis.sample.GenericException;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
import org.junit.rules.TestRule;
import org.junit.runners.model.Statement;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.junit.matchers.JUnitMatchers.both;
 
 
public final class ExpectedGenericException implements TestRule {
    private Matcher<Object> matcher;

    private ExpectedGenericException() {
        super();
    }

    @Override
    public Statement apply(final Statement base, final org.junit.runner.Description description) {
        return interceptedStatmentBase(base);
    }

    private Statement interceptedStatmentBase(final Statement base) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                try {
                    base.evaluate();
                } catch (final GenericException e) {
                    assertThat(e, matcher());
                    return;
                }
                if (matcher() != null)
                    throw new AssertionError("Expected exception to comply to was a Generic exception" + StringDescription.toString(matcher()));
            }

        };
    }


    public static ExpectedGenericException expected() {
        return new ExpectedGenericException();
    }


    public <E extends GenericException> void expect(final Matcher<Integer> matching, final Class<E> exceptionClass, final Matcher<String> message) {
       expect(instanceOf(exceptionClass));
        if (message != null)
            expect(hasMessage(message));
        expect(hasCode(matching));
    }



    public void expect(final Matcher<? extends Object> matching) {
        if (matcher == null)
            matcher= (Matcher<Object>)matching;
        else
            matcher= both(matcher).and(matching);
    }


    private Matcher<GenericException> hasCode(final Matcher<Integer> matcher) {
        return new GenericExceptionCodeMatcher(matcher);
    }

    private Matcher<GenericException> hasMessage(final Matcher<String> matcher) {
		return new GenericExceptionMessageMatcher(matcher);
	}

    private Matcher<Object> matcher() {
        return matcher;
    }


As in our previous example the apply method offers a possible entry point where, after decorating the incoming base statement with our own statement, we resend it. Our stament will basically filter a generic exception raise and apply defined Junit matchers. 
The matchers can be registered through the invocation of the expected method from the testing class. The expect method, that reuses the hamcrest both method, chains all the matchers in charge of validating the exception content. We there reuse the code pattern of the default ExpectedException in order to merge the differente matching expectations.

 
public void expect(final Matcher<? extends Object> matching) {
        if (matcher == null)
            matcher= (Matcher<Object>)matching;
        else
            matcher= both(matcher).and(matching);
    }



We basically match a class type, a message content, then a code value. In order to gracefully merge the matchers in a chain of matchers we also decorates each of them into package scoped instances of the classes GenericExceptionMessageMatcher and GenericExceptionCodeMatcher:

 
import com.promindis.sample.GenericException;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.internal.matchers.TypeSafeMatcher;

final class GenericExceptionCodeMatcher extends TypeSafeMatcher<GenericException> {
    private final Matcher<Integer> matcher;

    public GenericExceptionCodeMatcher(final Matcher<Integer> matcher) {
        this.matcher = matcher;
    }

    public void describeTo(final Description description) {
        description.appendText("exception with code ");
        description.appendDescriptionOf(matcher);
    }

    @Override
    public boolean matchesSafely(final GenericException item) {
        return matcher.matches(item.getCodeErreur());
    }
}

//And

import com.promindis.sample.GenericException;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.internal.matchers.TypeSafeMatcher;

final class GenericExceptionMessageMatcher extends TypeSafeMatcher<GenericException> {
    private final Matcher<String> matcher;

    public GenericExceptionMessageMatcher(final Matcher<String> matcher) {
        this.matcher = matcher;
    }

    public void describeTo(final Description description) {
        description.appendText("exception with message ");
        description.appendDescriptionOf(matcher);
    }

    @Override
    public boolean matchesSafely(final GenericException item) {
        return matcher.matches(item.getMessage());
    }
}

so we can add our own descriptions or whatever necessary. 

Let resist to the attempt of "generifying" more than necessary the wrappers and making our ExpectedGenericException more... generic. We are not building a framework. More complexity is not needed (YAGNI principle) 
Test green. We are done ! 

 Soon we will talk again about Clojure and Scala I hope, so be seeing you !!! :)

0 comments:

Post a Comment