From: Steve F. <sm...@us...> - 2002-08-12 23:10:23
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv9123/doc/xdocs Modified Files: doc-book.xml introduction.xml testing_guis_1.xml how_mocks_happened.xml preface.xml extra_entities.xml Log Message: reworking content Index: doc-book.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/doc-book.xml,v retrieving revision 1.9 retrieving revision 1.10 diff -u -r1.9 -r1.10 --- doc-book.xml 10 Aug 2002 23:43:41 -0000 1.9 +++ doc-book.xml 12 Aug 2002 23:10:20 -0000 1.10 @@ -17,6 +17,10 @@ <!ENTITY part_testing_guis SYSTEM "file://@docpath@/testing_guis_1.xml"> <!ENTITY chp_random SYSTEM "file://@docpath@/random.xml"> <!ENTITY notes SYSTEM "file://@docpath@/notes.xml"> + + <!ENTITY patterns SYSTEM "file://@docpath@/patterns.xml"> + <!ENTITY glossary SYSTEM "file://@docpath@/glossary.xml"> + %docbook; %extra_entities; ]> @@ -29,10 +33,10 @@ <firstname>Steve</firstname> <surname>Freeman</surname> <email>st...@m3...</email> </author> - <author> - <firstname>Nat</firstname> <surname>Pryce</surname> - <email>nat...@b1...</email> - </author> + <author> + <firstname>Nat</firstname> <surname>Pryce</surname> + <email>nat...@b1...</email> + </author> <copyright> <year>@year@</year> <holder>Steve Freeman</holder> @@ -62,15 +66,15 @@ <title>Living with Unit Tests</title> <chapter status="todo"> <title>Test organisation</title> - &todo; + &TODO; </chapter> <chapter status="todo"> <title>Test smells</title> - &todo; + &TODO; </chapter> <chapter status="todo"> <title>Retrofitting unit tests</title> - &todo; + &TODO; </chapter> </part> @@ -78,17 +82,17 @@ <chapter> <title>C++</title> - &todo; + &TODO; <comment><para>with Workshare?</para></comment> </chapter> <chapter> <title>Dynamic and metaprogramming</title> - &todo; + &TODO; </chapter> <chapter> <title>bash</title> - &todo; + &TODO; </chapter> </part> @@ -99,12 +103,25 @@ <part status="todo"> <title>Closing</title> - &todo; + &TODO; + </part> + + <part status="draft"> + <title>Appendices</title> + + <appendix status="draft"> + <title>Patterns</title> + <para>Some of the code patterns that we've referred to in the book.</para> + &patterns; + </appendix> + + <appendix status="todo"> + <title>The Expectation Library</title> + &TODO; + </appendix> + + ¬es; </part> - <appendix status="todo"> - <title>The Expectation Library</title> - &todo; - </appendix> - ¬es; + &glossary; </book> Index: introduction.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/introduction.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- introduction.xml 10 Aug 2002 23:43:41 -0000 1.2 +++ introduction.xml 12 Aug 2002 23:10:20 -0000 1.3 @@ -5,24 +5,90 @@ <title>Preface</title> <para> - In this part, we introduce Test-Driven Development. We only give a brief refresher on the basics, because there + In this part, we introduce &TDD;. We only give a brief refresher on the basics, because there are other books that are more suitable as an introduction to the technique. We do, however, spend some time introducing Mock Objects because that's how a group of us have been developing for several years and we like - what it does to our code. Let's get down to business. + what it does to our code. </para> </preface> <chapter status="todo"> <title>Test-Driven Development</title> + <mediaobject> + <imageobject><imagedata fileref="images/escher-hands.jpeg" format="jpeg" /></imageobject> + </mediaobject> + <section> - <title>Introduction and simple example</title> - &todo; + <title>Introduction: it's not about testing</title> + + <para> + <emphasis>&TDD; is not a testing technique, it's a design technique</emphasis>. + <footnote> + <para> + Or, as Keith Braithwaite put it so eloquently on the <glossterm linkend="wiki">Wiki</glossterm>, + <ulink url="http://c2.com/cgi/wiki?UnitInUnitTestIsntTheUnitYouAreThinkingOf"> + <quote>Unit in Unit Test isn't the Unit you are thinking of</quote> + </ulink> + </para> + </footnote> + Every book about &TDD; + should have that embossed in large friendly letters on its cover. &TDD; forces you, the programmer, to + think carefully about the next piece of functionality you're about to add. Before you write the code you + have to write a test that will describe exactly how you will know when you've succeeded. One way of looking + at &TDD; is as a <quote>Poor Man's Formal Methods</quote>, because it provides many of the intellectual + benefits of writing formal specifications without the overhead of learning a specification language + (such as <glossterm linkend="larch">Larch</glossterm> or <glossterm linkend="z">Z</glossterm>) but has + the powerful advantage that the results are executable. + &TDD; also forces you to pay attention to how easy your code will be to use, because the tests are the + first clients of the code—before it even exists. If you begin to find new tests hard to write, then + that tells you that it's time to spend some time working on the structure of the code, which means + <glossterm linkend="refactoring">Refactoring</glossterm>. + </para> + + <para> + &TDD; actually consists of two complementary practices, Test-first programming and Refactoring, that have a + symbiotic relationship. Test-first programming is about being focussed on what you need and precise about + what that means, but on its own it would result in scrappy, repetitive code. Refactoring is about taking + working code and improving its internal structure. Often we don't really understand what a solution should + look like until we've finished coding, no matter how much clever modeling we do. Most development shops, + however, don't refactor because (apart from holding the dubious view that code that seems to be working + is finished) it's often hard to predict what a change to the code base will do without careful investigation; + this makes refactoring risky and expensive. If we had a suite of tests to catch any errors that might arise + from making a change, then we could afford to be ambitious about improving the code. If we program test-first, + then we would have well-specified code with such a suite of tests. The two techniques really need each other. + </para> + + <para> + Once again, &TDD; is a design technique that has the valuable side-effect that it also produces a comprehensive + test suite. Design takes place when writing tests: specifying what the code should do, how its clients will + call it, and how the pieces fit together. Design also takes place when refactoring: removing duplication, + extracting and naming concepts, moving behaviour to the right places. &TDD; makes explicit that design takes + place throughout software development and that low-level code design is as important as high-level architecture. + </para> + + <para> + Model-Driven Development is about deferred gratification: I won't write code until I think I have a good + model of the structure of the problem (or the deadline is too close). &TDD; is a different kind of + deferral: I won't spend too much time thinking about the large-scale structure of the problem until I think + I've learned enough from writing and refactoring the code. When the time comes, the code will tell me how + it should be structured and I'll trust myself to see it. Some developers are uncomfortable with this + trade-off because it violates everything we've been taught about Good Practice since our first algorithms + course, and it clashes with the organisational structures most of us have always worked in. I've come to + prefer it because it's a closer match to my experience of how software development really works—and, + anyway, I never was much good at deferring gratification. + </para> </section> - <section> - <title>A quick tour of JUnit</title> - &todo; + + <section status="TODO"> + <title>A simple example</title> + &TODO; </section> + </chapter> + + <chapter status="TODO"> + <title>A quick tour of JUnit</title> + &TODO; </chapter> &how_mocks_happened; Index: testing_guis_1.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/testing_guis_1.xml,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- testing_guis_1.xml 10 Aug 2002 23:43:41 -0000 1.6 +++ testing_guis_1.xml 12 Aug 2002 23:10:20 -0000 1.7 @@ -564,13 +564,13 @@ <chapter status="todo"> <title>More test cases</title> - &todo; + &TODO; </chapter> <appendix id="findNamedComponent" status="todo"> <title>Finding GUI components</title> - &todo; + &TODO; </appendix> </part> Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- how_mocks_happened.xml 11 Aug 2002 14:09:28 -0000 1.5 +++ how_mocks_happened.xml 12 Aug 2002 23:10:20 -0000 1.6 @@ -34,7 +34,7 @@ </para> <programlisting> -public class TestRobot { +public class RobotTest { public void testGotoSamePlace() { final Position POSITION = new Position(1, 1); @@ -251,29 +251,129 @@ <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 <classname>junit.framework.AssertionFailedError</classname> in the development + harder, we can trap the &junit; <classname>AssertionFailedError</classname> 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. + having to step through from the beginning of the test. Of course, this doesn't work in every case, but most of + the time it takes you to straight the heart of the problem. </para> + </section> <!-- Breaking apart the Robot --> + <section> + <title>More complex tests</title> + <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: + </para> + + <programlisting> +public void testMoveOnePoint() { + final Position DESTINATION = new Position(1, 0); + + Motor mockMotor = new Motor() { + private int moveCount = 0; + + public void move(MoveRequest request) { + assertEquals("Should be move", new MoveRequest(1, MoveRequest.SOUTH), request); + moveCount++; + assertEquals("Should be first move", 1, moveCount); + } + }; + + Robot robot = new Robot(mockMotor); + robot.setCurrentPosition(new Position(0, 0)); + robot.goto(DESTINATION); - <!-- TODO --> + assertEquals("Should be destination", DESTINATION, robot.getPosition()); +}</programlisting> + + <para> + The first assertion will fail if the <classname>Robot</classname> passes an incorrect + <classname>MoveRequest</classname> or <constant>null</constant>. The second assertion will fail if the + <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 + have tried either assertion. We can only tell whether this failure has happened <emphasis>after</emphasis> + we've finished <function>goto()</function>, so we need to record how many times <function>move()</function> + was called. We can't just push <varname>moveCount</varname> up from the anonymous <classname>Motor</classname> + class to the test method because, unfortunately, Java requires such variables to be declared + <token>final</token> and we can't change the value of a <type>final int</type>. There are two alternatives. + </para> + <para> + First we could use a <glossterm linkend="selfshunt">Self Shunt</glossterm> instead of an anonymous class. This + means that the test class itself implements <classname>Motor</classname> so the test has direct access + to its instance fields. For example: + </para> -<para>Now I know that my Robot class works I can write a real implementation -of the Motor interface:</para> + <programlisting> +public RobotTest extends TestCase implements Motor { -<programlisting> -public class OneSpeedMotor implements Motor { + // Implementation of Motor + private int moveCount = 0; public void move(MoveRequest request) { - turnBy(request.getTurn()); - advance(request.getDistance()); + assertEquals("Should be move", new MoveRequest(1, MoveRequest.SOUTH), request); + moveCount++; + assertEquals("Should be first move", 1, moveCount); } - &elipsis; -} -</programlisting> -<para>As my tests grow, I can refactor the various mock implementations + public void testMoveOnePoint() { + final Position DESTINATION = new Position(1, 0); + + Robot robot = new Robot(this); + robot.setCurrentPosition(new Position(0, 0)); + robot.goto(DESTINATION); + + assertEquals("Should be destination", DESTINATION, robot.getPosition()); + assertEquals("Should be one move", 1, moveCount); + } +}</programlisting> + + <para> + Alternatively, we could change the outer level data type, as follows: + </para> + + <programlisting> +public void testMoveOnePoint() { + final Position DESTINATION = new Position(1, 0); + final ArrayList expectedMoveRequests = new ArrayList(); + + 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"); + } + } + }; + Robot robot = new Robot(mockMotor); + + expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); + robot.setCurrentPosition(new Position(0, 0)); + robot.goto(DESTINATION); + + assertEquals("Should be destination", DESTINATION, robot.getPosition()); + assertEquals("Should be no more moves", 0, expectedMoveRequests.size()); +}</programlisting> + + <para> + In this version, I'm holding the details of the expected route in <varname>expectedMoveRequests</varname>. + <!-- TODO --> <comment>Under development</comment> + </para> + + <para> + You can chose which style you prefer. I have to admit that, unreasonably, I'm biased against Self Shunt. + I can never remember which methods belong to the test class and which to the shunt, and it's a little bit + harder to separate out the two aspects later on. That said, the second approach can be a but more complicated + to write. At this level, it's a matter of taste but you should probably use only one style across your team. + </para> + + <!-- TODO --> <comment>Under development</comment> + </section> + + <section> + <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> @@ -331,9 +431,10 @@ } </programlisting> -</section> <!-- Factoring out the motor --> +</section> -<section><title>What does this mean?</title> +<section> + <title>What does this mean?</title> <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 Index: preface.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/preface.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- preface.xml 10 Aug 2002 23:43:41 -0000 1.1 +++ preface.xml 12 Aug 2002 23:10:20 -0000 1.2 @@ -75,12 +75,12 @@ <section status="todo"> <title>Organisation</title> - &todo; + &TODO; </section> <section status="todo"> <title>Acknowledgements</title> - &todo; + &TODO; </section> </preface> Index: extra_entities.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/extra_entities.xml,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- extra_entities.xml 10 Aug 2002 23:43:41 -0000 1.5 +++ extra_entities.xml 12 Aug 2002 23:10:20 -0000 1.6 @@ -2,4 +2,6 @@ <!ENTITY greenbar SYSTEM "file:@docpath@/green_bar.xml"> <!ENTITY elipsis "<emphasis>[...]</emphasis>" > -<!ENTITY todo "<comment>To Do</comment>" > +<!ENTITY TDD "Test-Driven Development"> +<!ENTITY TODO "<comment>To Do</comment>" > +<!ENTITY junit "JUnit" > |