Sunday, 9 September 2012

Assertable Pattern: Fluent BDD style test assertions in Pure Java

I'm a fan of expressing unit test logic in terms of behaviour rather than a series of assertions which imply that behaviour. My long term favourite pattern of expressing behaviour has been to use the given, when, then style, for instance:

given(some prior state)
when(something occurs)
then(this should have happened)

Unfortunately, as a static and strongly typed language, Java doesn't make it easy to express things in these terms and you can quite quickly get tied in knots trying to express yourself fluently. For this reason a number of frameworks like EasyB provide an abstraction over the top of the programming language in order to do this easily - and provide a means for non programmers to write tests.

However, the downside is that this logic must at some point be connected to the underlying language, and so you can get into the situations where you're writing Java/Java-like code in a non .java file. At which point your IDE doesn't know what is going on and syntax highlighting - let alone refactorings - no longer work. This is the worst of both worlds.

Therefore, I try to stick with pure Java. Because I don't like asserts, I write what I call "Assertable" classes, which make the assertions, but express them in a more natural way. When combined with a few chained function calls, you can start to make things fairly reasonable.

Suppose I have a bean called "Field" with a couple of getters on it, which I want to test. I can do a bit of assertion in the classic JUnit style:

assertEquals("The field should be required", true, field.isRequired());
assertEquals("The field should not be visible", false, field.isVisible());

I can keep going like this, but a few copy and pastes down the line and my test is a mess, and reading the thing is just so exhausting.

In this case I like to write a nice helper class, which:

  • Doesn't use the word assert - because non-programmers don't use words like that.
  • Is chainable, so you can avoid repetition.
  • Splits the expectation from the test, so you can modify your checks easily.
  • Sits in a different file, so its internal ugliness is hidden and its functions can be reused.
  • Provides helpful failure messages

My FieldAssertable class can be used in a test like this:

thenThe(field)
        .shouldBe().required()
        .shouldNotBe().visible();

This is still Java, but a non Java programmer can read and understand it, and my tests look cleverer.

Here is an implementation of FieldAssertable:

public class FieldAssertable {

  private Field field;
  private boolean booleanExpectation;

  public FieldAssertable(Field field) {
    this.field = field;
  }

  public FieldAssertable shouldBe() {
    this.booleanExpectation = true;
    return this;
  }

  public FieldAssertable shouldNotBe() {
    this.booleanExpectation = false;
    return this;
  }

  public FieldAssertable required() {
    assertEquals(
        getNiceFailureMessage("required"),
        this.booleanExpectation,
        field.isRequired());
    return this;
  }

  public FieldAssertable visible() {
    assertEquals(
        getNiceFailureMessage("visible"),
        this.booleanExpectation,
        field.isVisible());
    return this;
  }

  private String getNiceFailureMessage(String property) {
    return "Field `" + field.getId() + "` " +
           (this.booleanExpectation ? "should be " : "should not be ") +
           property;
  }

}

To finish, I add a means of accessing the Assertable without needing to know its name - using a method that can be statically imported into your test.

public static FieldAssertable thenThe(Field f) {
  return new FieldAssertable(f);
}

This doesn't solve the problem of allowing non-programmers to write acceptance tests (although I think that is always difficult, even with a framework), and it does still require an amount of OO hoop jumping, but it works for me, and it might work for you.

No comments:

Post a Comment