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> |