26 December 2006

Mock Objects is a Technique, Not a Technology

George Malamidis recently proposed an alternative to mock objects in Groovy. Instead of using mock objects you can just use Groovy's Expando class to make an object that behaves like another type and checks that it is being invoked as expected.

But... that is a mock object!

Mock objects are not a specific technology, such as jMock or EasyMock they are a technique for the test-driven development of object-oriented code that follows a Tell, Don't Ask design style.

You can write mock objects with a framework, by using the reflection and metaprogramming facilities of a dynamic language, by coding everything by hand, or by generating the code at compile time. Whatever floats your boat.

However, faking out the mocked type is only a small part of what a mock object framework like jMock does. In fact, of the 2487 lines of code that make up jMock 2 only 34 are required to perform what we whimsically call "imposterisation". The remainder provide syntactic sugar to ensure the test code clearly expresses the intent of the test, let the programmer precisely specify the expected interactions between objects and generate clear diagnostics when tests fail. This functionality is vital if tests are to be helpful during the ongoing development of the code.

14 December 2006

Only Mock Immediate Neighbours (i)

We often see tests that are hard to understand because they drive code that calls objects returned from other objects. Here's an example of code that calls a chain of objects.
public void allUnresolvedIssues(IssueHandler handler) throws Failure {
 TicketManager ticketManager = connections.getTicketManager();

 ticketManager.lock();
 try {
   IssueManager  issueManager = ticketManager.getIssueManager();
   Iterator issues = issueManager.allIssuesIterator();

   while (issues.hasNext()) {
     Issue issue = issues.next();
  
     if (issue.isUnresolved()) {
       handler.accept(issue);
     }
   }
 } finally {
   ticketManager.unlock();
 }
}
This is a minor offender, but even so it gets a list of Issues from an IssueManager, which it gets from a TicketManager, which it gets from a Connection. The important part of the method is the filtering of the issues, but to get there it has to navigate three objects, including locking and unlocking the TicketManager. We might have to do something like this to test it.
public void testFiltersUnresolvedIssuesFromIssueManager() throws Failure {
 final ArrayList allIssues = new ArrayList() {{
   add(issue1Unresolved);
   add(issue2);
   add(issue3Unresolved);
 }};

 expects(new InAnyOrder() {{
   allowing(connections).getTicketManager(); will(returnValue(ticketManager));

   expects(new InThisOrder() {{
     one(ticketManager).lock();
     one(ticketManager).getIssueManager(); will(returnValue(issueManager));
     one(issueManager).allIssuesIterator(); will(returnValue(allIssues.iterator()));
  
     one(issueHandler).accept(issue1Unresolved);
     one(issueHandler).accept(issue3Unresolved);
  
     one(ticketManager).unlock();
   }});
 }});

 moreThanImmediateNeighbours.allUnresolvedIssues(issueHandler);
}
I find this confusing to read. First, too much of the test code is for setting up state to get to the interesting feature. Second, the test is exercising several things at once: locking and unlocking the TicketManager, querying the issues within the lock, and filtering the issues. If I want to test more conditions, such as when there are no issues or when something fails, I'll have to duplicate lots of irrelevant test code. I can extract that duplication into setup methods, but I think that's missing what the test is telling me. Another problem is that the test is, more or less, just reproducing the logic of the code, which means it's not adding much value and also makes it brittle when I want to refactor later—especially if I have many similar tests to change. This is a common reason given for not using Mock Objects and a plausible one when dealing with code like this. Let's see if I can break things up a bit. My first thought is that I can separate out everything to do with the TicketManager by extracting a method.
  public void allUnresolvedIssues(IssueHandler handler) throws Failure {
   TicketManager ticketManager = connections.getTicketManager();
  
   ticketManager.lock();
   try {
     retrieveUnresolvedIssues(ticketManager.getIssueManager(), handler);
   } finally {
     ticketManager.unlock();
   }
 }

 private void retrieveUnresolvedIssues(IssueManager issueManager, IssueHandler handler) {
   Iterator issues = issueManager.allIssuesIterator();
  
   while (issues.hasNext()) {
     Issue issue = issues.next();
    
     if (issue.isUnresolved()) {
       handler.accept(issue);
     }
   }
 }
Now I can see a bit more clearly the two activities going on here: getting hold of the issue manager, and retrieving issues from it. I can break these up into different, focussed objects. I could start from the current design and refactor, but I want to begin again to show the design process...

01 December 2006

Evolving a Domain-Specific Language in Java

I've uploaded our OOPLSA 2006 paper, Evolving a Domain-Speficic Language in Java which describes our experience of evolving from the mockobjects library to jMock (with jMock2 coming soon !). In particular, we write how things changed when stumbled on the notion that we were writing a language rather than just a library. Our one regret is that for copyright reasons we had to take out the cute dinosaur pictures we found before publication. We even found an Agilisaurus.