Share and Enjoy

David Saff's blog of technological joys.

Wednesday, December 20, 2006

 

Interface imposterization

I learn all sorts of things from jMock. From jMock 1, I was infected with the idea of a fluent interface for creating mocks, and the power of Constraint objects. Lately, I've been playing with jMock 2, and it's had me thinking even more deeply about a pattern I'm for the moment calling interface imposterization.*

To explain imposterization, I'd like to make a distinction between two kinds of subtypes of a given type (for this discussion, let's consider implementations of a Java interface). An implementor of an interface satisfies all of the documented and implied contracts for that interface. An impostor of an interface is also an implementation, as far as the object runtime is concerned, but is free to violate the interface contracts, in order to learn or assert or learn a property about the code that uses it.

If you've run across mock objects in unit testing, then you've already seen one instance of imposterization. Consider constructing a mock for a BankAccount interface, in order to test a woefully primitive transaction processor, using jMock 1 syntax:
interface BankAccount {
void deposit(int amount);
void withdraw(int amount);
int getBalance();
}

@Test public void readDeposit() {
BankAccount account = mock(BankAccount.class);
account.expects(once()).method("deposit").with(eq(1000));
TransactionReader reader = new TransactionReader(account);
reader.readLine("deposit 1000");
}
Here, account is an impostor of the BankAccount interface. It's not really a proper implementor, because there are all kinds of contracts, perhaps documented, and perhaps implied, that our mock BankAccount breaks. For one thing, it probably throws an expectation exception when getBalance() is called, something that no proper implementation of BankAccount would do. But we're not trying to create a general-purpose implementation--the point of our imposterization is to learn something about the readLine method: does it interact in the right way with account?

jMock 2 also uses imposterization to set expectations on mock objects. Here's the same test in jMock 2 syntax:

@Test public void readDeposit() {
BankAccount account = mock(BankAccount.class);
expects(new InAnyOrder() {{
one(account).deposit(1000);
}});
TransactionReader reader = new TransactionReader(account);
reader.readLine("deposit 1000");
}
Here, the result of one(account) is a different impostor, which records a method call (.deposit(1000)) as a method call expected to be called later in the test. one(account) doesn't make any pretense of being a real BankAccount. From the point of view of the intent of object-oriented design, this is almost pure evil. But it is useful, because it reduces the "meta-noise" of the test. Rather than having to invent a new language to talk about a method invocation, we can use regular Java to just invoke the method itself.**

Impostors are not a new idea to me--my thesis work on test factoring involves creating "capturing decorators" to record invocations, and mocks to replay them--both kinds of impostors that we saw above. This is done automatically to create unit tests from arbitrary program executions, and run them after program changes, without the developer having to really know what's going on under the hood.

The new idea from jMock 2 is that plain-Java interfaces for creating impostors can be quite elegant and succinct. Knowing this, more and more problems begin to suggest imposterization to me. In my next post, I'll talk about using impostors to simplify testing exception-throwing methods, and later, we'll look at using impostors to automatically generate stubs for verifying Theories.

* The name imposterization comes from the interface Imposteriser in jMock 2. However, I won't claim that the jMock authors had exactly the same idea of what imposterization means, and I'll just have to agree to disagree about the -ize vs. -ise ending, thanks to Samuel Johnson.

** The idea of using an interface impostor to record mock object expectations has been around in EasyMock (and perhaps other frameworks) for a while. The benefits of this syntax are not without costs--I have always been uncomfortable with EasyMock's two-phase mock objects, which first capture and then replay, with a global method call in the middle to switch state. That smells to me of a missed abstraction. jMock 2 also, it turns out, uses two-phase mocks under the covers, but the interface at least encourages thinking about the recording phase differently, since developers call one(account).deposit(1000) instead of directly account.deposit(1000). But I still have the same concerns that something is being missed.

Comments:
That's an excellent description of what an Imposter is. When I was naming the Imposteriser interface in jMock 2 I discussed its name with some colleagues. They argued that it was creating Adapters (and so should be an Adapteriser). I was sure that it was not, but couldn't pin down why I thought an Imposter was not an Adapter.

As for the two-phase style of jMock 2. I didn't intend the one(...) method to be the reminder for the user (although it's interesting that it is). Rather, all literal calls that define expectations are written in the expects block between great honkin' double-braces. The idea was to create a clear visual distinction between expectations and actual calls, to ensure that expectations are specified declaratively and that the API does not appear to be stateful so that it is easy to understand when you read it later.
 
Post a Comment





<< Home

Archives

February 2005   June 2005   March 2006   August 2006   December 2006   April 2007   May 2007   January 2008  

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]