From: Steve F. <sm...@us...> - 2001-11-05 02:38:44
|
Update of /cvsroot/mockobjects/mockobjects-java/doc/xdocs/papers In directory usw-pr-cvs1:/tmp/cvs-serv29591 Modified Files: brief_introduction.html Log Message: more contents Index: brief_introduction.html =================================================================== RCS file: /cvsroot/mockobjects/mockobjects-java/doc/xdocs/papers/brief_introduction.html,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- brief_introduction.html 2001/11/03 00:33:31 1.3 +++ brief_introduction.html 2001/11/05 02:38:38 1.4 @@ -1,10 +1,8 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> - <title>Developing JDBC applications test-first</title> - + <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} @@ -19,34 +17,36 @@ </style> </head> <body bgcolor="#ffffff"> -<h1>How Mock Objects happen</h1> +<h1>How Mock Objects happened</h1> <p align="Center">Steve Freeman <tt> <st...@m3...></tt></p> <h2>Introduction</h2> -<p>Unit testing is a fundamental practice in Extreme Programming, but -most nontrivial code is difficult to test in isolation. You need to -make sure that you test one feature at a time, and you want to be -notified as soon as any problem occurs. Normal unit testing is hard -because you are trying to test the code from outside. </p> - -<p>Mock Objects is an approach to writing unit tests based on two key -techniques: 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>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> +<p>This article assumes that you are familiar with java and unit +testing using JUnit.</p> <h2>Simple unit tests</h2> -<p>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> +<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 { [...] @@ -114,7 +114,7 @@ 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 MoveRequests since the last +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 @@ -128,10 +128,10 @@ <h2>Factoring out the motor</h2> -<p>One thing I can be sure of is that the Robot contains a Motor 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> +<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); @@ -225,24 +225,86 @@ 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> -<h2>What has this change achieved?</h2> -<ul> - <li><em>Refactored test infrastructure</em> As my tests grow, I find myself repeating my test - infrastructure. I can push those repetitions into mock implementations, based on the - expectation types that ship with the Mock Objects library. </li> - <li><em>Failing early</em> A key experience of developing test-first is learning to make test - failures self explanatory. Failing at the time an error occurs with a useful message, - rather than at the end of the test, is a powerful tool.</li> - <li><em>Driving design</em> Choosing which objects to verify in a test turns out to be an - effective tool for deriving the structure of a class and its neighbours.</li> - <li><em>Consistent test structure</em> Most unit tests in this style have a similar structure: - set up the objects (pre-conditions), set the expected behaviour (invariants and - post-conditions), exercise the object, and then verify whether the expectations were - fufilled. 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.</li> -</ul> <hr> <p>© Steve Freeman 2001</p> </body> |