From: Steve F. <sm...@us...> - 2002-08-18 22:44:44
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv29932/doc/xdocs Modified Files: glossary.xml testing_guis_1.xml how_mocks_happened.xml patterns.xml notes.xml htmlbook.css Log Message: more work on how mocks happened. Index: glossary.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/glossary.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- glossary.xml 12 Aug 2002 23:07:58 -0000 1.1 +++ glossary.xml 18 Aug 2002 22:44:39 -0000 1.2 @@ -2,6 +2,14 @@ <title>Glossary</title> <glossdiv> + <title>D</title> + + <glossentry id="dtsttcpw"> + <glossterm>Do The Simplest Thing That Could Possibly Work</glossterm> + </glossentry> + </glossdiv> <!-- D --> + + <glossdiv> <title>L</title> <glossentry id="larch"> Index: testing_guis_1.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/testing_guis_1.xml,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- testing_guis_1.xml 12 Aug 2002 23:10:20 -0000 1.7 +++ testing_guis_1.xml 18 Aug 2002 22:44:39 -0000 1.8 @@ -567,6 +567,16 @@ &TODO; </chapter> + <chapter status="todo"> + <title>Notes</title> + <para> + When do you make the flip from testing the GUI from outside to inserting a model that you can mock? + What makes it difficult is that the Swing components are all classes, not interfaces, so it's impossible + to mock up a component and pass it in to the top-level frame. What that drives us to is that the frame + just assembles the pieces and does layout, and the pieces talk to each other via interfaces in a gui model. + What's hard is finding the right time to introduce that gui model. + </para> + </chapter> <appendix id="findNamedComponent" status="todo"> <title>Finding GUI components</title> Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.8 retrieving revision 1.9 diff -u -r1.8 -r1.9 --- how_mocks_happened.xml 18 Aug 2002 01:42:12 -0000 1.8 +++ how_mocks_happened.xml 18 Aug 2002 22:44:39 -0000 1.9 @@ -6,8 +6,8 @@ <para> Mock Objects is a development technique that lets you unit test classes that you didn't think - you could <emphasis>and</emphasis> helps you write better code while doing so. In this chapter - we use a simple example to show a compressed history of how Mock Objects was discovered by + you could <emphasis>and</emphasis> helps you write better code while doing so. This chapter + works through a simple example to show a compressed history of how Mock Objects was discovered by refactoring from conventional unit tests and why they're useful. </para> @@ -17,6 +17,14 @@ <emphasis>inside</emphasis> those mock implementations so that you can validate the interactions between the objects in your test case as they occur. </para> + + <para> + This chapter is unusual in that it's focussed on the tests, it ignores the production code almost completely. + I'll be covering the whole cycle in later chapters but, for now, I want to concentrate + on the thought processes behind a particular approach to test-driven development. This chapter is not about + how to develop routing software for a robot, but how to structure your code so that you can tell if such a + module works. + </para> </section> <!-- Introduction --> <section> @@ -47,11 +55,11 @@ }</programlisting> <para> - This test tells us that the robot is in the same place after the test as it was before the test. - That's an essential requirement, but it's not enough. We can't be sure that the robot hasn't - trundled all the way around the warehouse before returning; we need to know that the robot hasn't - moved. How about if the robot were to store the route it takes each time we call - <methodname>goTo()</methodname>? We could retrieve the route and make sure it's valid. For example: + This test tells us that the robot is in the same place after the run as it was before the run. + That's an essential requirement, but it's not enough. We need to be sure that the robot hasn't + trundled all the way around the warehouse before returning. How about if the robot were to store + the route it takes each time we call <methodname>goTo()</methodname>? Then we could check the number + of steps it took. For example: </para> <programlisting> @@ -67,9 +75,9 @@ }</programlisting> <para> - So far we've specified that the robot will end up in the same place if that's where we ask it to go. We've - also specified that it will store the route from it's most recent move, and something about - the programming interface that it should support. Our next test is to move one point on the grid: + Our first test specifies that the robot will not move if we tell it to go to the same place, that + it will store the route from it's most recent move, and something about the programming interface it + should support. The next meaningful action we can think of is to move just one point on the grid: </para> <programlisting> @@ -88,15 +96,15 @@ <sidebar> <para> - Of course, you've noticed that we haven't said anything about the layout of the warehouse. We'll + You may have noticed that we haven't said anything about the layout of the warehouse. We'll assume for now that the Robot knows about it by some other mechanism. </para> </sidebar> <para> - Now we've also specified the simplest routing scheme, for moves to adjacent squares, and that we're - describing each leg of the route with a <classname>MoveRequest</classname> object. We carry on and - pretty soon we're moving the robot all over the building, for example: + This test specifies a simple routing scheme, for moves to adjacent squares, and that we're + describing each step of the route with a <classname>MoveRequest</classname> object. We carry on and + pretty soon we have tests for moving the robot all over the building, for example: </para> <programlisting> @@ -109,7 +117,7 @@ assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); assertEquals("Should be same moves", - makeExpectedLongWayMoves(), robot.getRecentMoves()); + makeExpectedLongWayMoves(), robot.getRecentMoveRequests()); }</programlisting> <para> @@ -124,10 +132,10 @@ <para> Tests like this are effectively small-scale integration tests, they set up pre-conditions and - test post-conditions. There are advantages to this technique because test failures can reveal interesting - dependencies between classes and help to drive refactorings. The disadvantage is that the tests - has no access to the code while it is running. If our last test failed, the error report would - say something like: + test post-conditions but they say nothing about what happens in-between. There are advantages to this technique + because test failures can reveal interesting dependencies between classes, which can suggest useful + refactorings. The disadvantage is that the tests have no access to the code while it is running. If + our last test failed, the failure report would say something like: </para> <screen> @@ -149,16 +157,16 @@ Second, to test this behaviour at all, we had to add some functionality to the real code, to hold all the <classname>MoveRequest</classname>s since the last <methodname>goTo()</methodname>. We don't have any other immediate need for <methodname>getRecentMovesRequests()</methodname> and, - what's worse, we've made an implicit promise to other people who work with the Robot code that we - will store routes, thus shrinking (ever so slightly) our room to manouver. We can mark the method as - test infrastructure, but that sort of notation is clumsy and tends to be ignored in the heat of development. + what's worse, we've made an implicit promise to other people who work with the <classname>Robot</classname> + code that we will store routes, thus shrinking (ever so slightly) our room to manouver. We can mark the method as + test infrastructure, but this sort of notation is clumsy and tends to be ignored in the heat of development. </para> <para> 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 getting too busy and - that some behaviour should be moved from the utility to the class. + that some behaviour should be moved from the utility to a real production class. </para> <para> @@ -169,7 +177,7 @@ of avoiding getters? <footnote><para> John Nolan is credited with asking this question at the right time at a software architecture - meeting in London + meeting in London. </para></footnote> </para> </section> <!-- what's wrong with this --> @@ -197,7 +205,7 @@ First we define an interface for <classname>Motor</classname>. We know that there must be some kind of action, which we'll call <methodname>move()</methodname>, that takes some kind of move request as a parameter. We don't yet know what's in that request, so we'll define an empty <classname>MoveRequest</classname> - interface as a placeholder to get us through the compiler. (Of course, in dynamic languages, such as Smalltalk + interface as a placeholder to get us through the compiler. (In dynamic languages, such as Smalltalk and Python, we don't even need to do that.) </para> @@ -216,7 +224,7 @@ </para> <para> - Now we have to decide what the test implementation will do. In this test, we want to be sure that the + We have to decide what the test implementation will do. In this test, we want to be sure that the <classname>Robot</classname> stays in place, so the test <classname>Motor</classname> should simply fail if it receives any requests to move. We can write this test now, before we know anything else about the system. The new version of the test is: @@ -249,10 +257,12 @@ </sidebar> <para> - We have just locked down a little piece of the specification. We can be sure that, however complex our - routing code gets, the <classname>Robot</classname> will not move if asked to go to its current position. - From now on, if there's a bug in the robot routing code that asks the motor to move, the test will fail at the - point that the request is made. The error report might look like: + This version of the test still describes the same core specification as before (the + <classname>Robot</classname> will not move if asked to go to its current position), but it no longer insists + that the <classname>Robot</classname> hold on to its recent movements. The new test is just a little bit more + precise. The other interesting difference is that, if there's a bug in the robot routing code that asks the + motor to move, the test will fail <emphasis>at the point that the request is made</emphasis>. + The error report might look like this: </para> <screen> @@ -269,9 +279,9 @@ <para> which gives a much clearer view of where the error became visible. If finding the problem turns out to be - harder, we can trap the &junit; <classname>AssertionFailedError</classname> in the development + harder, we can trap the &junit; <classname>AssertionFailedError</classname> exception in the development tool to bring up the debugger. Then we can explore the program state at the time of the failure, without - having to step through from the beginning of the test. Of course, this doesn't work in every case but, more + having to step through from the beginning of the test. This technique doesn't work all the time but, more often than not, it takes you to straight to the source of the problem. </para> </section> <!-- Breaking apart the Robot --> @@ -281,14 +291,14 @@ <para> Now let's revisit the second test, moving to an adjacent space. We want to ensure that exactly one move request has been made and that it contains the right values. Testing that the request is made at most - once is straightforward: + once is straightforward. </para> <programlisting> public void testMoveOneStep() { final Position DESTINATION = new Position(1, 0); - Motor mockMotor = new Motor() { + <emphasis>Motor mockMotor = new Motor() { private int moveCount = 0; public void move(MoveRequest request) { @@ -296,7 +306,7 @@ moveCount++; assertEquals("Should be first move", 1, moveCount); } - }; + };</emphasis> Robot robot = new Robot(mockMotor); robot.setCurrentPosition(new Position(0, 0)); @@ -308,10 +318,10 @@ <para> The first assertion will fail if the <classname>Robot</classname> sends the wrong <classname>MoveRequest</classname>. The second assertion will fail if the - <classname>Robot</classname> calls <function>move()</function> more than once. Unfortunately, this test won't + <classname>Robot</classname> calls <function>move()</function> more than once. This test won't fail if the <classname>Robot</classname> doesn't call <function>move()</function> at all, as it won't - try either assertion. We can only check for this kind of failure <emphasis>after</emphasis> - <function>goTo()</function> has finished, so we need to know how many times <function>move()</function> + try either assertion. We can only tell whether this has happened after + <function>goTo()</function> has finished, so we need to record how many times <function>move()</function> was called. Unfortunately, we can't just push <varname>moveCount</varname> up from the anonymous <classname>Motor</classname> class to the test method because a quirk of Java requires such variables to be declared <token>final</token> and we can't change the value of a <type>final int</type>. I can think of @@ -375,12 +385,16 @@ <para> Personally, I prefer the first of these two options because I find it easier to see the boundaries - between the stub and the test classes. As you'll see shortly, I think that makes handling sequences of - events easier. That said, there's not a huge difference, there are plenty of people making effective use - of Self Shunt, and it's more important that your team is consistent about its approach. + between the stub and the test classes. That said, there's not a huge difference, there are plenty of + people making effective use of Self Shunt, and it's more important that your team is consistent + about its approach. <tip> + <title>Be consistent</title> <para> - The most important thing is for the team to have a consistent approach to writing unit tests. + There are many unit testing techniques, and it's important to understand what each one does. + It's more important, however, for the team to have a consistent approach to unit testing than + to pick the perfect strategy each time. This reduces the intellectual overhead when you're + working on an unfamiliar area of the code. </para> </tip> </para> @@ -391,8 +405,8 @@ <title>Testing more than one step</title> <para> - Now we want to test a route that takes two steps which we need to check. Our current test implementation - can only handle one step, so we need to introduce a collection. In this test we need to be sure that the + Now we want to test the contents of a route that takes two steps. Our current test implementation + can only handle one, so we need to introduce a collection. In this test we need to be sure that the steps are taken in the right sequence, so we'll use an <classname>ArrayList</classname> to describe the <classname>MoveRequest</classname>s that we expect the <classname>Robot</classname> to generate. </para> @@ -401,13 +415,14 @@ public void testMoveTwoSteps() { ArrayList expectedMoveRequests = new ArrayList(); - expectedMoveRequests.add(new MoveRequest(1, MoveRequests.SOUTH)); - expectedMoveRequests.add(new MoveRequest(2, MoveRequests.WEST); + expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); + expectedMoveRequests.add(new MoveRequest(2, MoveRequest.WEST); }</programlisting> <para> - We can use to the <varname>expectedMoveRequests</varname> in the mock motor, and cross off the - actual move requests as they arrive. + We can use the <varname>expectedMoveRequests</varname> in the mock motor. Every time an actual + move arrives from the robot, we pull out the next value from the list of expected moves and compare the + two. If there's a mismatch then our routing software has gone wrong and the test fails. </para> <programlisting> @@ -420,8 +435,8 @@ } };</emphasis> - expectedMoveRequests.add(new MoveRequest(1, MoveRequests.SOUTH)); - expectedMoveRequests.add(new MoveRequest(2, MoveRequests.WEST)); + expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); + expectedMoveRequests.add(new MoveRequest(2, MoveRequest.WEST)); Robot robot = new Robot(mockMotor); @@ -431,9 +446,19 @@ assertEquals("Should be destination", new Position(-1, 2), robot.getCurrentPosition()); }</programlisting> + <note> + <para> + In Java <function>remove(0)</function> removes the first element of the list and returns it to the + caller. + </para> + </note> <para> - Which will fail if the robot asks the mock motor to make an incorrect move. We need to add a little more - code to handle the cases where the robot generates too many or too few steps. + We have two more conditions to check. If the robot does not generate enough moves, then not all the + expected moves will be consumed. As before, we need to check at the end that there's nothing left over. + Alternatively, if the robot generates too many moves then calling <function>remove(0)</function> on an + empty list will throw an <classname>IndexOutOfBoundsException</classname> exception. The &junit; framework + will catch that as an error but it's worth translating to a meaningful failure, so we catch the exception and + call <function>fail()</function>. </para> <programlisting> @@ -450,11 +475,10 @@ } }; - expectedMoveRequests.add(new MoveRequest(1, MoveRequests.SOUTH)); - expectedMoveRequests.add(new MoveRequest(2, MoveRequests.WEST)); + expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); + expectedMoveRequests.add(new MoveRequest(2, MoveRequest.WEST)); Robot robot = new Robot(mockMotor); - robot.setCurrentPosition(new Position(0, 0)); robot.goTo(new Position(-1, 2)); @@ -463,124 +487,222 @@ }</programlisting> <para> - Now we have a technique that will test any sequence of steps that we need, including one step or none. We - can rework our original tests for consistency. + Some of you might be asking how introducing a list of expected move requests is better than the + original <quote>playback</quote> version that recorded the most recent movements. The answer is that, + by moving the collection from the production code into the test infrastructure, we've made our design testable + without adding functionality that the system doesn't need. We've kept our options a little bit more open and, + as a side benefit, we have much more useful failure messages. </para> - <programlisting> -public void testMoveOneStep() { - final Position DESTINATION = new Position(1, 0); + </section> <!-- Testing more than one step --> - Motor mockMotor = new Motor() { - public void move(MoveRequest request) { - try { - assertEquals("Should be move", expectedMoveRequests.remove(0), request); - } catch (IndexOutOfBoundsException ex) { - fail("Should be more moves"); - } - } - }; + <section> + <title>Refactoring the tests</title> - expectedMoveRequests.add( new MoveRequest(1, MoveRequest.SOUTH)); + <para> + Now that we have a dynamic list as part of our test infrastructure, we don't really need special test code + to handle the 0-step and 1-step cases any more. I'm going to jump straight to the end result, rather + than take you through the refactorings step by step, to save paper (or pixels, if you're reading + this on-line) but I believe it's straightforward. Here's the entire test class. + </para> + + <programlistingco> + <areaspec> + <area id="fields" coords="3" /> + <area id="robotandmotor" coords="11" /> + <area id="testsameplace" coords="24" /> + <area id="testonestep" coords="29" /> + <area id="testtwosteps" coords="35" /> + <area id="moveandverify" coords="42" /> + </areaspec> + <programlisting> +public class RobotTest extends TestCase { + private Robot robot; + private ArrayList expectedMoveRequests = new ArrayList(); - Robot robot = new Robot(mockMotor); - robot.setCurrentPosition(new Position(0, 0)); - robot.goTo(DESTINATION); + public RobotTest(String name) { + super(name); + } - assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); - assertEquals("Should be no more moves", 0, expectedMoveRequests.size()); -} + public void setUp() { + robot = new Robot( + new Motor() { + public void move(MoveRequest request) { + try { + assertEquals("Should be move", expectedMoveRequests.remove(0), request); + } catch (IndexOutOfBoundsException ex) { + fail("Too many moves"); + } + } + }); + } -public void testGotoSamePlace() { - final Position POSITION = new Position(1, 0); + public void testGotoSamePlace() { + final Position POSITION = new Position(1, 1); + moveAndVerifyRobot(POSITION, POSITION); + } - Motor mockMotor = new Motor() { - public void move(MoveRequest request) { - try { - assertEquals("Should be move", expectedMoveRequests.remove(0), request); - } catch (IndexOutOfBoundsException ex) { - fail("Should be more moves"); - } - } - }; + public void testMoveOneStep() { + expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); - Robot robot = new Robot(mockMotor); - robot.setCurrentPosition(POSITION); - robot.goTo(POSITION); + moveAndVerifyRobot(new Position(0, 0), new Position(1, 0)); + } - assertEquals("Should be destination", POSITION, robot.getCurrentPosition()); - assertEquals("Should be no more moves", 0, expectedMoveRequests.size()); -} -</programlisting> + public void testMoveTwoSteps() { + expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); + expectedMoveRequests.add(new MoveRequest(2, MoveRequest.WEST)); + + moveAndVerifyRobot(new Position(0, 0), new Position(-1, 2)); + } + + private void moveAndVerifyRobot(Position start, Position destination) { + robot.setCurrentPosition(start); + robot.goTo(destination); + + assertEquals("Should be destination", destination, robot.getCurrentPosition()); + assertEquals("Should be no more moves", 0, expectedMoveRequests.size()); + } +}</programlisting> - <para> - There's a lot of repetetive code here. I think we should do something about that. + <calloutlist> + <callout arearefs="fields"><para> + The <varname>robot</varname> and <varname>expectedMoveRequests</varname> are used throughout the + class, so we've made them instance fields. + </para></callout> + <callout arearefs="robotandmotor"><para> + There is now just one implementation of the <classname>Motor</classname> for all the tests. We + don't even need to refer to it after we've built it because we're doing all the test configuration + through <varname>expectedMoveRequests</varname>. We set up the <classname>Robot</classname> and + construct its mock <classname>Motor</classname> implementation inline. + </para></callout> + <callout arearefs="testsameplace"><para> + This test says that, if we're going to the same place, then we expect no movement. We've used a + local constant variable <varname>POSITION</varname> to emphasise that the start and destination + are the same. We leave the <varname>expectedMoveRequests</varname> list empty. + </para></callout> + <callout arearefs="testonestep testtwosteps"><para> + These tests make sure that, if the robot moves, it follows the correct route. The only thing a + test needs is start and destination positions and an ordered list of steps. + </para></callout> + <callout arearefs="moveandverify"><para> + All the tests do the same thing: position the robot, ask it to move, and then check the results. + This method implements that sequence. Using <varname>expectedMoveRequests</varname> to describe + the route means that the test for too few moves is always the same. If we'd stuck with the + <quote>playback</quote> version, we would have had to specify the expected route size for each test. + </para></callout> + </calloutlist> + </programlistingco> + + <para> + In practice, I would probably jump straight in using <varname>expectedMoveRequests</varname> for the 0-step + and 1-step cases because I'm used to it and there's not much overhead in the implementation. That's reasonable + provided I don't take too long to write the first test because I'm trying to think too far ahead + and over-generalise. + + <tip> + <title>Go to Lourdes</title> + <para> + Throw away your software crutches occasionally to test your assumptions just one more time; + this is one of the movitivations for the XP rule + <glossterm linkend="dtsttcpw"><quote>Do The Simplest Thing That Could Possibly Work</quote></glossterm>. + I'm always surprised how much better I can do something the next time around after I have more + experience. For example, I've found that writing the examples for this book a real eye-opener + because I've had to work out some of my practices from first principals again. + (Lourdes is a shrine in France famous for healing the lame, the walls are covered in discarded crutches.) + </para> + </tip> </para> - </section> + </section> <!-- Refactoring the tests --> <section> + <title>Extracting a MockMotor class</title> - <!-- TODO --> <comment>Under development</comment> - <title>Under development</title> - <para>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:</para> + <para> + The next thing we might consider doing is to extract the anonymous test implementation of + <classname>Motor</classname> into a <classname>MockMotor</classname> class. This moves the + <classname>expectedMoveRequests</classname> from the tests into the new class. + </para> + + <programlisting> +public class MockMotor implements Motor { + private ArrayList expectedMoveRequests = new ArrayList(); -<programlisting>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); + try { + assertEquals("Should be move", expectedMoveRequests.remove(0), request); + } catch (IndexOutOfBoundsException ex) { + fail("Too many moves"); + } } - public void addExpectedRequest(MoveRequest request) { - this.expectedRequests.add(request); + + public void addExpectedMove(MoveRequest request) { + expectedMoveRequests.add(request); } + public void verify() { - assertEquals("Too few requests", 0, this.expectedRequests.size()); + assertEquals("Should be no more moves", 0, expectedMoveRequests.size()); } }</programlisting> -<para>Which makes our tests look like:</para> + <para> + Starting a new class costs a little extra, but it does bring some benefits. + First, it makes the tests a bit more legible because it moves the behaviour + (managing lists of move requests) to the object that should know about it + (the <classname>Motor</classname>). If we rework one of our tests, it comes out as: + </para> -<programlisting>public class TestRobot { - &elipsis; - 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); + <programlisting> +public class RobotTest extends TestCase { + <emphasis>private MockMotor mockMotor = new MockMotor();</emphasis> + private Robot robot = new Robot(<emphasis>mockMotor</emphasis>); - assertEquals("Should be same", ORIGIN, robot.getCurrentPosition()); - mockMotor.verify(); - } - public void testMoveOnePoint() { - final Position DESTINATION = new Position(1, 0); + public void testMoveTwoSteps() { + <emphasis>mockMotor.addExpectedMove(new MoveRequest(1, MoveRequest.SOUTH)); + mockMotor.addExpectedMove(new MoveRequest(2, MoveRequest.WEST)); </emphasis> - mockRobot.addExpectedRequest(new MoveRequest(1, MoveRequest.SOUTH)); + moveAndVerifyRobot(new Position(0, 0), new Position(-1, 2)); + } - robot.goTo(DESTINATION); + private void moveAndVerifyRobot(Position start, Position destination) { + robot.setCurrentPosition(start); + robot.goTo(destination); - assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); - mockMotor.verify(); + assertEquals("Should be destination", destination, robot.getCurrentPosition()); + <emphasis>mockMotor.verify();</emphasis> } - public void testMoveALongWay() { - final Position DESTINATION = new Position(34, 71); + &elipsis; +}</programlisting> - mockMotor.addExpectedRequests(makeExpectedLongWayMoveRequests()); + <para> + I find this version more readable because it expresses clearly that we're setting expectations about + what the robot will do to the motor; it doesn't rely on a side effect, that the + <varname>expectedMoveRequests</varname> is referenced by the fake <classname>Motor</classname> + implementation. The new version also describes those expectations in the language of the motor, + <function>addExpectedMove()</function>, rather than in terms of an object collection. + </para> - robot.goTo(DESTINATION); + <para> + Second, if you have more tests that describe talking to a <classname>Motor</classname>, perhaps + because there are different kinds of robot, then basic code hygiene will force you to pull the + test functionality out into a separate class where it can be reused. One way of looking at the + entire Mock Object approach to testing is that it's simply refactoring assertions out from multiple + test cases. + </para> - assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); - mockMotor.verify(); - } -} -</programlisting> + <tip> + <title>Steady promotion</title> + <para> + Often, I evolve a mock class by starting with an anonymous implementation in the test class, + and then extracting a separate mock class if the code becomes anything more than trivial. The mock + class may start as an inner class of the test case (because my development environment makes + that refactoring easy), which I'll promote to package level if I need it elsewhere. + </para> + </tip> + + </section> <!-- Extracting a MockMotor class --> + + <!-- TODO --> <comment>Under development</comment> -</section> <section> <title>What does this mean?</title> @@ -646,6 +768,13 @@ out to be the flex points I need as the use of the code evolves. </para> </section> <!-- What does this mean? --> + + <section status="todo"> + <title>Notes</title> + <!-- TODO --> + <para>Top-down decomposition</para> + <para>Design is the choice of object to test</para> + </section> <section> <title>Conclusions</title> Index: patterns.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/patterns.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- patterns.xml 17 Aug 2002 12:59:10 -0000 1.2 +++ patterns.xml 18 Aug 2002 22:44:39 -0000 1.3 @@ -1,6 +1,28 @@ <glossary status="draft"> <glossdiv> + <title>A</title> + + <glossentry id="arguethetest"> + <glossterm>Argue about the test, not the code</glossterm> + &TODO; + </glossentry> + </glossdiv> <!-- H --> + + <glossdiv> + <title>H</title> + + <glossentry id="howcanwetell"> + <glossterm>How could we tell?</glossterm> + &TODO; + <para> + The first question when adding funtionality is always, "How could we tell if it worked". Often this translates + to "which object would know?", or "What do we verify()?". + </para> + </glossentry> + </glossdiv> <!-- H --> + + <glossdiv> <title>S</title> <glossentry id="selfshunt"> Index: notes.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/notes.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- notes.xml 2 Aug 2002 21:56:03 -0000 1.1 +++ notes.xml 18 Aug 2002 22:44:39 -0000 1.2 @@ -1,8 +1,8 @@ <part status="first cut"> - <title>To Do</title> - <itemizedlist> - <listitem><para>Tests in Ant</para></listitem> - <listitem><para>bash mocks</para></listitem> - <listitem><para><emphasis>DocBook</emphasis>Fix keysym, guibutton, co</para></listitem> - </itemizedlist> + <title>To Do</title> + <itemizedlist> + <listitem><para>Tests in Ant</para></listitem> + <listitem><para><emphasis>DocBook</emphasis>Fix keysym, guibutton, co</para></listitem> + <listitem><para>Needs pictures, UML</para></listitem> + </itemizedlist> </part> Index: htmlbook.css =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.css,v retrieving revision 1.9 retrieving revision 1.10 diff -u -r1.9 -r1.10 --- htmlbook.css 18 Aug 2002 01:42:12 -0000 1.9 +++ htmlbook.css 18 Aug 2002 22:44:39 -0000 1.10 @@ -12,7 +12,8 @@ .programlisting { margin-left: 5%; } .screen { margin-left: 5%; } .sidebar { border: double black 1px; font-size: 80%; padding: 4px; text-align: center; margin-left: 70%; } -.tip { border: double black 1px; font-size: 80%; padding: 2px; } +.tip { border: double black 1px; font-size: 80%; } +.note { border: double black 1px; font-size: 80%; } .screenshot { margin-left: 20%; } .caption { font-size: 80%; font-style: italic; } .guibutton { font-weight: bold; } |