The Source for Java Technology Collaboration
User: Password:



Tom White

Tom White's Blog

Literate Programming with jMock

Posted by tomwhite on May 11, 2006 at 11:10 AM | Comments (7)

We've been using jMock at our company for some time now. We've found it great for test driven development and isolating our unit tests from the rest of the system more effectively. One aspect of jMock that stands out for me is its idea of constraints. In fact, we've found this idea so useful that we always use the org.jmock.MockObjectTestCase base class rather than junit.framework.TestCase, even when we aren't mocking anything out. This seems to have registered with the jMock development team, as they are planning to extract the constraints into a separate project, codenamed Hamcrest (it's an anagram of "matchers").

We can use constraints to construct more flexible assertions.

assertThat(a, eq("3"));

is more readable and understandable than

assertEquals("3", a);

You can read it as "assert that a eq(uals) 3". (The only reason it is not equals is that it conflicts with Object's equals method.) This form makes the code more literate, that is, easier for humans to read, which is the main goal of Donald Knuth's idea of literate programming.

The assertThat form is very extensible too - you just write new constraints. We've grown a small library of constraints - a sort of DSL for unit testing. The library contains constraints ranging from the simple conveniences isNull and isNotNull, to more complex collections constraints such as includes which takes a variable number of objects (varargs) that the collection must contain:

assertThat(list, includes("peach", "pear", "plum"));

They are all easy to write.

Consequences

One thing we missed from JUnit assertions was the ability to control the failure message. This isn't often needed as the describeTo method of the constraint does a good job in most cases. There are occasions however when you need more context, such as when you have several boolean assertions in a single test and you want to distinguish them. For example, the following test

public void test() {
    boolean a = true;
    boolean b = false;
    assertThat(a, eq(true));
    assertThat(b, eq(true));
}

fails with a message that doesn't tell you if it is a or b that is false:

junit.framework.AssertionFailedError:
Expected: eq()
    got : false

We extended jMock's base test class org.jmock.MockObjectTestCase to take an overloaded version of assertThat that takes a new parameter called a Consequence. The test uses the otherwise method that creates a Consequence instance with the given failure message,

public void test() {
    boolean a = true;
    boolean b = false;
    assertThat(a, eq(true), otherwise("a should be true"));
    assertThat(b, eq(true), otherwise("b should be true"));
}

and fails more helpfully:

junit.framework.AssertionFailedError: b should be true

Literate Functional Tests

My colleague Robert Chatley, who worked on the jMock extensions described above, has recently taken the idea of literate testing further and applied it to functional (acceptance) tests. He has built a framework for testing a markup language for digital TV services, where as well as the assertions, the steps for driving the test are literate. Here is a sample:

public void testArrivalsWithUnambiguousOriginAndUnambiguousDestination() throws Exception {

    goToTheTrainArrivalsSearchScreen();

    enter(LONDON_PADDINGTON, into(textBox().labelled("Arriving at")));
    enter("Bristol Temple Meads", into(textBox().labelled("Calling at")));
    enter("1554", into(textBox().labelled("Arriving after")));
    select(option("Today"), from(selectBox().labelled("When")));

    clickOn(button("Select"));

    assertThat(theCurrentPage, has(title("Train arrivals after 15:54 today")));
}

Notice that you can't tell that it is testing a digital TV service. Those details are hidden behind the small collection of methods that allow you to perform actions: goTo() (called by goToTheTrainArrivalsSearchScreen()), enter(), select(), clickOn(); or extract data from the page: textBox().labelled("Arriving at"), button("Select").

Taking a tip from Nat Pryce, we can apply a neat trick and change Eclipse's settings so that punctuation is white, to make the code even more readable (but less editable)! (Steve Yegge had the same idea for Scheme.) This is what you get:

public void testArrivalsWithUnambiguousOriginAndUnambiguousDestination() throws Exception {

    goToTheTrainArrivalsSearchScreen();

    enter(LONDON_PADDINGTON, into(textBox().labelled("Arriving at")));
    enter("Bristol Temple Meads", into(textBox().labelled("Calling at")));
    enter("1554", into(textBox().labelled("Arriving after")));
    select(option("Today"), from(selectBox().labelled("When")));

    clickOn(button("Select"));

    assertThat(theCurrentPage, has(title("Train arrivals after 15:54 today")));
}

Truly literate.


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Hi Tom,

    One thing I hate in jmock is that jmock invokes some target methods via reflection, and I think it is the obstacle in refactoring, say rename the method name.

    Look at this example:
    mockSubscriber.expects(once()).method("receive").with( eq(message) );
    .

    if I try rename the "receive" name, I have to search "receive" string in whole project. Do you have any better solution?

    /Jack

    Posted by: jacktang on May 12, 2006 at 08:29 AM

  • Hi Jack,

    This is quite a frequent criticism of jMock. Personally, I haven't found this much of a problem. Sure it catches me out now and again, but the unit test fails and I fix it - not too hard. Also, because mocks allow you to tightly constrain the method being tested (by mocking out its dependencies), renaming a single method normally only affects a small number of tests.


    If you find it a problem then don't use jMock's mock object support. In the post I was saying that jMock is useful even if you don't use mock objects since it provides a nicer assertion syntax.


    Tom

    Posted by: tomwhite on May 12, 2006 at 12:38 PM

  • Hello Tom

    Why not switch to Smalltalk, where you can write all your code literally, not just the silly test cases ;-).

    Martin

    Posted by: skamar on May 15, 2006 at 05:33 AM

  • Harlow Tom,

    Have you tried the rMock mocking framework? How would you compare jMock and rMock?

    Posted by: shaolang on May 15, 2006 at 06:16 AM

  • I quite like the JMock style. It strikes me as a bit of a shame, however, that you have to resort to things like this in Java because the language itself isn't flexible enough.

    Jack - if you rename a method in IntelliJ IDEA, it shows you occurrences in strings and comments as well, giving you the option to rename them as well. Don't know if other IDEs do this as well.

    Posted by: calumm on May 17, 2006 at 12:51 PM

  • Reads quite nice, I've used a similar technique. See Here

    Posted by: ervine_s on June 13, 2006 at 10:55 AM

  • Hi Calumm,
    eclipse does this too. You just have to check the boxes in the renaming dialog box and it will search for test literals everywhere you want it too.

    Posted by: edovale on September 14, 2006 at 09:54 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds