From: Steve F. <sm...@us...> - 2002-09-01 00:30:13
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv10906/doc/xdocs Modified Files: how_mocks_happened.xml Log Message: bit more on design issues Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.14 retrieving revision 1.15 diff -u -r1.14 -r1.15 --- how_mocks_happened.xml 22 Aug 2002 11:28:31 -0000 1.14 +++ how_mocks_happened.xml 1 Sep 2002 00:30:11 -0000 1.15 @@ -21,7 +21,7 @@ <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 + on the thought processes behind a particular approach to &TDD;. 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> @@ -159,14 +159,14 @@ 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 <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. + test infrastructure, but this sort of notation is clumsy and is often 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 a real production class. + that some behaviour should be moved there from the utility. </para> <para> @@ -734,25 +734,53 @@ program, writing tests with Mock Objects makes us think about the objects it depends on, its environment. Sometimes those objects don't yet exist, which means that we've found a conceptual <quote>fault line</quote> in the system that we can turn into - a new object interface. In our tests, we describe what the objects should say to each other, + a new object interface. In our tests, the expectations describe what the objects should say to each other, their <emphasis>protocol</emphasis>, without having to figure out how the new objects actually - work. Applied consistently, this pushes us towards a codebase made up of components with - clear interfaces which (so I've been led to believe) makes good things like reusability + work. We fake them up for now and move on to implementing them when we're ready. + Applied consistently, this pushes us towards a codebase made up of components with + clear interfaces which (at least in my experience) makes good things like reusability happen almost as a byproduct. What makes &TDD; so powerful is that we can do - all this incrementally, evolving the code from the requirements in hand rather than - having to place bets on the what we think we're going to need. + this incrementally, evolving the code from the requirements in hand rather than + trying to place bets on the what we think we're going to need. + </para> + <para> + One more design issue is our unusually heavy use of the <quote>Tell, don't ask</quote> pattern. + We prefer to pass behaviour into an object, rather than pulling values out, which is why we wrote + a <quote>route following object</quote> rather than returning a collection of + <classname>MoveRequest</classname>. + Our experience is that the common use of getters, particularly for collections, makes a class and + the code that uses it that little bit harder to change. Across a whole code base, the increased + <quote>refactoring drag</quote> can be significant. </para> + <note> + <para> + If it's not clear why getting collections can slow things down, think of all the times + you write iterator loops such as: + </para> + <programlisting> +Iterator moveRequests = robot.getMoveRequests(); +while (moveRequests.hasNext()) { + MoveRequest moveRequest = (MoveRequest)moveRequests.next(); + <lineannotation>// process moveRequest</lineannotation> +}</programlisting> + <para> + If we change to passing a <classname>Motor</classname> to a <classname>Robot</classname> which, in turn, + passes each step on the route to the <classname>Motor</classname>, then we've refactored the loop navigation. + At the very least, we've made the tests more accurate. If we need to iterate over this collection in more + than one place, then we've also removed some duplication. Some of us think that this style is also more + expressive because it leaves decisions about route management with the <classname>Robot</classname>, rather + than the caller. + </para> + </note> <para> - We want to unit test but don't want to expose more of the internals of the object than is necessary, - because that makes the class, and the code that uses it, that little bit harder to change; across a - whole code base, the increased <quote>refactoring drag</quote> can be significant. Our solution - (<quote>Tell, don't ask</quote>) forces us into passing around computation rather than data, which is - why we used the <quote>route following object</quote>, rather than returning a collection - of <classname>MoveRequest</classname>. + Speculating wildly, the next requirements might push us to extract a <classname>Navigator</classname> + object that works out the route, and the <classname>Robot</classname> becomes just a frame that + holds together the objects that do the real work. This is how the process often works: we start with a + top level object, fill in some implementation, and hollow it out as we understand more about what's + inside. We then do the same to the new objects we've created, and so on. </para> - <sidebar> <title>Top-Down Decomposition</title> <para> @@ -769,9 +797,9 @@ and to drive the design of interfaces from their use rather than their implementation. It also had a couple of critical failings: it turned out to be hard to change early design decisions because, of course, they're embedded in the top-level structure, and it's not good at - encouraging reuse between lower-level components. It looks like &TDD; avoids those failures because - object-orientation makes it easier to share and replace components throughout the application and - because its emphasis on refactoring means we can adjust as the codebase grows. + encouraging reuse between lower-level components. It looks like &TDD; avoids these problems because + object-orientation makes it easier to share components throughout the application and + the emphasis on refactoring means we can remove duplication as the codebase grows. </para> </sidebar> </section> <!-- What about design? --> @@ -788,19 +816,19 @@ <para>My code moved in this direction because I was committed to unit testing but didn't want to record and expose unnecessary details about the -state of my Robot +state of my Robot (the <function>getRecentMoveRequests()</function> method). As a result, I have better unit tests and a cleaner interface to the Robot class. But even better than that, by following the needs of my tests I have actually ended up improving the object model of the Robot by separating the Robot from its Motor and defining the an abstract interface between the two. -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 +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 its motor:</para> <programlisting> -/** A decorator that accumulates distances, then passes the request +/** A decorator that accumulates distances, then passes the request * on to a real Motor. */ public class MotorTracker implements Motor { @@ -816,10 +844,10 @@ } public long getTotalDistance() { return totalDistance; - } + } } -// When constructing the Robot, wrap the implementation of a +// 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); |