|
From: Steve F. <sm...@us...> - 2001-11-05 02:48:56
|
Update of /cvsroot/mockobjects/mockobjects-java/doc/xdocs/papers
In directory usw-pr-cvs1:/tmp/cvs-serv32038
Added Files:
how_mocks_happened.html
Log Message:
renamed from brief_introduction
--- NEW FILE: how_mocks_happened.html ---
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>How Mock Objects happened</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<style type="text/css">
<!--
.deemphasised { color: #666666}
h1 { text-align: center; font-family: Arial, Helvetica, sans-serif; font-weight: bold}
h3 { font-family: Arial, Helvetica, sans-serif; font-style: italic; font-weight: bold; font-size: small}
.inline_code { font-family: "Courier New", Courier, mono; font-style: normal; vertical-align: middle}
p { font-family: Arial, Helvetica, sans-serif; margin-left: 5%}
li { font-family: Arial, Helvetica, sans-serif }
h2 { font-family: Arial, Helvetica, sans-serif; margin-top: 3%}
pre { margin-left: 7%}
-->
</style>
</head>
<body bgcolor="#ffffff">
<h1>How Mock Objects happened</h1>
<p align="Center">Steve Freeman <tt> <st...@m3...></tt></p>
<h2>Introduction</h2>
<p>Mock Objects is a development technique that lets you unit test
classes that you didn't think you could <em>and</em> helps you write
better code whilst doing so. This article uses a simple example to
show a compressed history of how Mock Objects was discovered by
refactoring from conventional unit tests and what its advantages
are.</p>
<p>Mock Objects is based on two key concepts: replace everything
except the code under test with mock implementations that emulate the
rest of the environement, and put the test assertions <em>inside</em>
those mock implementations so that you can validate the interactions
between the objects in your test case. </p>
<p>This article assumes that you are familiar with java and unit
testing using JUnit.</p>
<h2>Simple unit tests</h2>
<p>Let's begin with an example. Most people start writing unit
tests by creating an object, calling some methods on it, and then
checking its state. For example, imagine that I'm writing a component
automatically to direct robots through a warehouse. Given a start
point on a grid, it will find a route through the stacks to a given
destination. The tests will have to tell me that the robot arrives
correctly and does not hit anything on the way. A first test might be:
</p>
<pre>public class TestRobot {
[...]
public void setUp() {
robot = new Robot();
}
public void testGotoSamePlace() {
final Position POSITION = new Position(1, 1);
robot.setCurrentPosition(POSITION);
robot.goTo(POSITION);
assertEquals("Should be same", POSITION, robot.getPosition());
}
}</pre>
<p>This tells me that the robot thinks that it has arrived, but it
doesn't tell me anything about how it got there. I would like to know
that the Robot didn't move in this test. One option would be to store
and retrieve the route it took after each <em>goto()</em>. For
example:</p>
<pre>public void testGotoSamePlace() {
final Position POSITION = new Position(0, 0);
robot.setCurrentPosition(POSITION);
robot.goTo(POSITION);
assertEquals("Should be empty", 0, robot.getRecentMoveRequests().size());
assertEquals("Should be same", POSITION, robot.getPosition());
}</pre>
<p>My next test might be to move one point on the grid:</p>
<pre>public void testMoveOnePoint() {
final Position DESTINATION = new Position(1, 0);
robot.setCurrentPosition(new Position(0, 0));
robot.goto(DESTINATION);
assertEquals("Should be destination", DESTINATION, robot.getPosition());
List moves = robot.getRecentMoveRequests();
assertEquals("Should be one move", 1, moves.size());
assertEquals("Should be same move",
new MoveRequest(1, MoveRequest.SOUTH), moves.get(1));
}</pre>
<p>As the tests become more complex, I would pull out some of the
detail into helper methods to make the code more legible:</p>
<pre>public void testMoveALongWay() {
final Position DESTINATION = new Position(34, 71);
robot.setCurrentPosition(new Position(0, 0));
robot.goto(DESTINATION);
assertEquals("Should be destination", DESTINATION, robot.getPosition());
assertEquals("Should be same moves",
makeExpectedLongWayMoves(), robot.getRecentMoves());
}</pre>
<p>Where <em>makeExpectedLongWayMoves()</em> returns another list of
the moves I expect the robot to take for this test. </p>
<p>There are problems with this approach to testing. First, tests like
this are effectively small-scale integration tests, they set up
pre-conditions and test post-conditions but they have no access to the
code while it is running. If one of these tests fail, I will have to
step through the code to find the problem because the assertions have
been made <em>after</em> the call has finished. Second, to test this
behaviour at all, I had to add some functionality to the production
class, namely retaining all the <em>MoveRequest</em>s since the last
<em>goto()</em>. I don't have any other immediate need for
<em>getRecentMovesRequests()</em>. Third, although it's not the case here,
test suites based on extracting history from objects tend to need lots
of utilities for constructing and comparing collections of values. The
need to write external code to manipulate an object is often a warning
that its class is incomplete and that some behaviour should be moved
from the utility to the class.</p>
<p>Is there a better way? Can I find a style that will give me better
error reporting and put the behaviour in the right place? </p>
<h2>Factoring out the motor</h2>
<p>One thing I can be sure of is that the Robot contains a
<em>Motor</em> that responds to these move requests. If I can
intercept those requests, I can track what's happening in the Robot. A
first step would be to define an interface to the Motor:</p>
<pre>public interface Motor {
void move(MoveRequest request);
}</pre>
<p>I can pass an instance of Motor to the Robot, perhaps via its
constructor, and a simple implementation might be:</p>
<pre>public class OneSpeedMotor implements Motor {
public void move(MoveRequest request) {
turnBy(request.getTurn());
advance(request.getDistance());
}
[...]
}</pre>
<p>I can now refactor my tests and replace the real Motor with a
MockMotor that can watch what's happening in the Robot, and complain as
soon as something goes wrong. The first test is now:</p>
<pre>public void testGotoSamePlace() {
final Position POSITION = new Position(0, 0);
robot.setCurrentPosition(POSITION);
Motor mockMotor = new Motor() {
public void move(MoveRequest request) {
fail("There should be no moves in this test");
}
};
robot.setMotor(mockMotor);
robot.goTo(POSITION);
assertEquals("Should be same", POSITION, robot.getPosition());
}</pre>
<p>In this test, if there is a bug in the Robot code and the Motor
gets requested to move, the mock implementation of <em>move()</em>
will fail immediately and stop the test; I no longer need to ask the Robot
where it's been. As my tests grow, I can refactor the various mock implementations
into a single, more sophisticated MockMotor and use it throughout all the Robot
tests; for example:</p>
<pre>public void MockMotor implements Motor {
private ArrayList expectedRequests = new ArrayList();
public void move(MoveRequest request) {
assert("Too many requests", this.expectedRequests.size() > 0);
assertEquals("Should be next request",
this.expectedRequests.remove(0), request);
}
public void addExpectedRequest(MoveRequest request) {
this.expectedRequests.add(request);
}
public void verify() {
assertEquals("Too few requests", 0, this.expectedRequests.size());
}
}</pre>
<p>Which makes our tests look like:</p>
<pre>public class TestRobot {
[...]
static final Position ORIGIN = new Position(0, 0);
public void setUp() {
mockMotor = new MockMotor();
robot = new Robot(mockMotor);
robot.setCurrentPosition(ORIGIN);
}
public void testGotoSamePlace() {
robot.goTo(ORIGIN);
assertEquals("Should be same", ORIGIN, robot.getPosition());
mockMotor.verify();
}
public void testMoveOnePoint() {
final Position DESTINATION = new Position(1, 0);
mockRobot.addExpectedRequest(new MoveRequest(1, MoveRequest.SOUTH));
robot.goto(DESTINATION);
assertEquals("Should be destination", DESTINATION, robot.getPosition());
mockMotor.verify();
}
public void testMoveALongWay() {
final Position DESTINATION = new Position(34, 71);
mockMotor.addExpectedRequests(makeExpectedLongWayMoveRequests());
robot.goto(DESTINATION);
assertEquals("Should be destination", DESTINATION, robot.getPosition());
mockMotor.verify();
}
}</pre>
<h2>What does this mean?</h2>
<p>My code moved in this direction because I was committed to unit
testing but didn't want to expose unnecessary details about the state
of my Robot (the <em>getRecentMoveRequests()</em> method). As a
result, I find that I have better unit tests and that I have clarified
some of the internal structure of the Robot by adding a
<em>Motor</em> interface. I now have the flexibility to subtitute a
different Motor implementation, perhaps one that
accelerates. Similarly, if I want to track the total distance a Robot
travels, I can do so without changing the implementation of either the
Robot or Motor:</p>
<pre>
/**
* A decorator that accumulates distances, then passes the request
* on to a real Motor.
*/
public class MotorTracker implements Motor {
private Motor realMotor;
private long totalDistance = 0;
public MotorTracker(Motor aRealMotor) {
realMotor = aRealMotor;
}
public void move(MoveRequest request) {
totalDistance += request.getDistance();
realMotor.move(request);
}
public long getTotalDistance() {
return totalDistance;
}
}
// When constructing the Robot, wrap the implementation of a
// Motor that does the work in a MotorTracker.
OneSpeedMotor realMotor = new OneSpeedMotor();
MotorTracker motorTracker = new MotorTracker(realMotor);
Robot = new Robot(motorTracker);
// do some travelling here [...]
println("Total distance was: " + motorTracker.getTotalDistance());
</pre>
<p>Neither changing the Motor implementation nor adding tracking
functionality would have been so easy if I had stuck with a testing
strategy based on stashing the intermediate route. The next step might
be to introduce a <em>Navigator</em> object to work out the route,
and the Robot would then link the two together.</p>
<p>Tests based on Mock Objects usually conform to a pattern: setup any
state, set expectations for the test, run the target code, and verify
that your expectations have been met. This style makes tests easy to
work with because they look similar and because all the interactions
with an object are local to a test fixture; I have found myself
contributing usefully to someone else's code base after only a few
minutes with the tests. More importantly, however, I constantly find
that the process of deciding what to verify in a test drives me to
clarify the relationships between an object and its collaborators.
The flex points I add to my code to provide support for testing turn
out to be the flex points I need as the use of the code evolves.
</p>
<h2>Conclusions</h2>
<p>This simple example shows how refactoring tests with some design
principles in mind led to the discovery of an unusually fruitful
development technique. Using Mock Objects in practice is slightly
different. First, the process of writing a test usually involves
defining which mock objects are involved, rather than extracting them
afterwards. Second, in Java at least, there are the beginnings of a
Mock Object library for common types. Third, there are now several
tools and libraries to help with the construction of Mock Objects. In
particular, the <a
href="http://www.mockobjects.com">www.mockobjects.com</a> site
includes a library of expectation objects.
</p>
<hr>
<p>© Steve Freeman 2001</p>
</body>
</html>
|