Continuing a series of write-ups on the presentations made at “Geek Night”:http://geeknight.thoughtworks.com/index.php/GeekNightLondon last week, “Nat Pryce’s”:http://nat.truemesh.com/ latest project OO-Matron is next up.
Rather than the evolutionary approach of JBehave, OO-Matron might potentially be revolutionary, or might just be too complicated for people to use – I’m still undecided. It’s designed to replace existing Mocking API’s, whilst at the same time adding the ability to enforce system-wide invariants to your tests, which in turn can be used to provide system documentation.
When you specify your codes behaviour, you are normally testing an object’s behaviour in certain states – “If I do this, then pass that in, I expect method A to be called on class B”. OO-Matron takes this further and actually models these allowed states internally. It concentrates on providing a way of specifying the logical state of a system (e.g. the socket is connected after a successful call to connect) rather than specifying physical state (e.g. the socket is connected if the fileDescriptor property is non-zero), which is primarily what JUnit is used for. Concentrating on the logical specification of states might be what makes OO-Matron potentially so useful – the amount of physical state specification that should be done in a proper OO system should be minimal, whereas defining the interactions between objects is key in OO.
The code doesn’t look that different compared to a traditional mock-based test (OO-Matron currently sits on top of JUnit):
public void testPingsRemoteHost() throws IOException {
String serverReply = "pong";
//Think of Roles as dependencies within the state you are modelling (mocks!)
Role socketFactory = newRole(SocketFactory.class, "socketFactory");
Role socket = newRole(Socket.class, "socket");
Pinger pinger = new Pinger((SocketFactory) socketFactory);
expect("factory should create one socket")
.on(socketFactory).method("newSocket").withNoArguments().will(returnValue(socket));
expect("remote server address")
.on(socket).method("connect").with(eq(SERVER_ADDRESS),eq(PING_PORT));
expect("socket should send 'ping' and receive 'pong'")
.on(socket).method("send").with(eq("ping"))
.method("recv").withNoArguments().will(returnValue(serverReply));
assertEquals("should return reply from server", serverReply, pinger.ping(SERVER_ADDRESS, PING_PORT));
}
OO-Matron doesn’t stop there however. It allows you to abstract out more specific specifications into a protocol, where you can specify system-wide expected behaviour. This protocol is then transparently checked when running tests – if any of your tests violate the invariants, then the test fails. The ability to define a protocol which is enforced during tests could be very useful – it gives an obvious point at which to generate system documentation, and you can ship the protocol with your API, so making it easy for end-users to provide extensions to your API which do not violate any system-wide constraints.
You can also draw obvious parallels between OO-Matron’s protocols and design by contract (DBC), but they also have obvious differences. Firstly DBC assigns pre-conditons to methods, whereas OO-Matron lets you assign pre-conditions to blocks of associated code (e.g. binding is done like this, connecting/sending/receiving is done like this). DBC also has no real understanding of the current state of an object when an Object is called – you cannot say “this precondition holds now, but might not hold in this other state” without exposing some public way of determining state – OO-Matron handles this as you explicity define the series of calls to get the object into any given state.
If a test fails because the code gets into an invalid state, you get a nice verbose message explaining what is allowed, and what you tried to do. In this example we made a mistake in our code that was picked up by our protocol:
junit.framework.AssertionFailedError: socket.send() not allowed in state disconnected of protocol socketConnecting of: socket.send( String arg1 ) socket.recv() socketConnecting in state disconnected allows only: socket.connect(not null,a valid TCP port) socket.connect(not null,a valid TCP port) throwing an instance of java.io.IOException of: socket.connect( java.net.InetAddress arg1, int arg2 ) ... expectation "remote server address" in state state1 allows only: socket.connect(eq(),eq()) of: socket.connect( java.net.InetAddress arg1, int arg2 ) expectation "socket should send 'ping' and receive 'pong'" in state state1 allows only: socket.send(eq()) of: socket.send(java.lang.String arg1)
Right now it is too early to say how useful OO-Matron will be. Other things talked about include the addition of visualisation tools to display the state of running tests (it solid be an easy job for the state definition to be displayed as UML state diagrams). Anyway, I’m certainly going to keep my eye on it – expect a more detailed overview in the future. Now if only someone could come up with a better name… Contact @npryce {at} users {dot} sf {dot} net@ or grab the code at “sourceforge”:http://www.sf.net/projects/xspecs for more details.
2 Responses to “OO-Matron”
Hi,
I have to say, great article. Reading your blog is what keep me up to date with Java Technology.
JBehave and OO-Matron concept is very interesting.
I am more to web development. Would you happen to have any good reference of JBehave or OO-Matron sample usage for testing jsp pages ?
Given their relative immiturity, there isn’t anything in the way of code to help with testing web stuff – you might find “Cactus”:http://jakarta.apache.org/cactus/ useful for performing integration testing.