You can subscribe to this list here.
2001 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(13) |
Aug
(151) |
Sep
(21) |
Oct
(6) |
Nov
(70) |
Dec
(8) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2002 |
Jan
(47) |
Feb
(66) |
Mar
(23) |
Apr
(115) |
May
(24) |
Jun
(53) |
Jul
(10) |
Aug
(279) |
Sep
(84) |
Oct
(149) |
Nov
(138) |
Dec
(52) |
2003 |
Jan
(22) |
Feb
(20) |
Mar
(29) |
Apr
(106) |
May
(170) |
Jun
(122) |
Jul
(70) |
Aug
(64) |
Sep
(27) |
Oct
(71) |
Nov
(49) |
Dec
(9) |
2004 |
Jan
(7) |
Feb
(38) |
Mar
(3) |
Apr
(9) |
May
(22) |
Jun
(4) |
Jul
(1) |
Aug
(2) |
Sep
(2) |
Oct
|
Nov
(15) |
Dec
(2) |
2005 |
Jan
(1) |
Feb
(1) |
Mar
|
Apr
(1) |
May
(28) |
Jun
(3) |
Jul
(11) |
Aug
(5) |
Sep
(1) |
Oct
(5) |
Nov
(2) |
Dec
(3) |
2006 |
Jan
(8) |
Feb
(3) |
Mar
(8) |
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: Steve F. <sm...@us...> - 2002-08-09 23:45:52
|
Update of /cvsroot/mockobjects/no-stone-unturned In directory usw-pr-cvs1:/tmp/cvs-serv5410 Modified Files: build.xml Log Message: Moved some filters Factored out _Transform Index: build.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/build.xml,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- build.xml 7 Aug 2002 21:52:27 -0000 1.4 +++ build.xml 9 Aug 2002 23:45:47 -0000 1.5 @@ -37,8 +37,6 @@ <echo message="ant.home = ${ant.home}"/> <echo message=""/> - <filter token="version" value="${project.version}"/> - <filter token="year" value="${year}"/> </target> <target name="CheckXslLibrary" @@ -114,6 +112,8 @@ <target name="_CopyFilesWithFiltering"> <!-- requires source.xml.file, target.xml.file --> + <filter token="version" value="${project.version}"/> + <filter token="year" value="${year}"/> <filter token="docpath" value="${basedir}/${out.xdoc.dir}"/> <filter token="xslpath" value="${basedir}/${xsl.dir}"/> <filter token="fragment.name" value="${fragment.name}"/> @@ -140,18 +140,27 @@ </antcall> </target> - <target name="html-book" - depends="PrepareBook" - description="Generate book as a single HTML file."> - - <delete file="${out.doc.dir}/book.html" /> - - <xslt basedir="${out.xdoc.dir}" - in="${out.xdoc.dir}/book.xml" - out="${out.doc.dir}/book.html" - style="${out.xdoc.dir}/htmlbook.xsl" + <target name="_Transform" > + <!-- requires input.file, output.file --> + + <delete file="${out.doc.dir}/${output.file}" /> + + <xslt basedir="${out.xdoc.dir}" + in="${out.xdoc.dir}/${input.file}" + out="${out.doc.dir}/${output.file}" + style="${out.xdoc.dir}/htmlbook.xsl" processor="trax" /> - </target> + </target> + + <target name="html-book" + depends="PrepareBook" + description="Generate book as a single HTML file."> + + <antcall target="_Transform"> + <param name="input.file" value="book.xml" /> + <param name="output.file" value="book.html" /> + </antcall> + </target> <target name="CheckFragmentProperties"> <fail unless="fragment.name" message="Requires -Dfragment.name=<fragment name>" /> @@ -169,19 +178,16 @@ depends="CheckFragmentProperties, PrepareFragment" description="Generate part of a book as a single HTML file."> - <delete file="${out.doc.dir}/${fragment.name}.html" /> - - <xslt basedir="${out.xdoc.dir}" - in="${out.xdoc.dir}/_template.xml" - out="${out.doc.dir}/${fragment.name}.html" - style="${out.xdoc.dir}/htmlbook.xsl" - processor="trax" /> + <antcall target="_Transform"> + <param name="input.file" value="_template.xml" /> + <param name="output.file" value="${fragment.name}.html" /> + </antcall> </target> - <target name="clean" - depends="CallMeFirst" - description="Remove all generated files"> - <delete dir="${xsl.dir}" /> - <delete dir="${out.dir}" /> - </target> + <target name="clean" + depends="CallMeFirst" + description="Remove all generated files"> + <delete dir="${xsl.dir}" /> + <delete dir="${out.dir}" /> + </target> </project> |
From: Nat P. <np...@us...> - 2002-08-09 18:49:58
|
Update of /cvsroot/mockobjects/no-stone-unturned In directory usw-pr-cvs1:/tmp/cvs-serv24074 Added Files: outline.txt Log Message: Added outline of book --- NEW FILE: outline.txt --- Part I : Intro to TDD 1. Introduction - who this book is for - what is the point of the book - admin 2. TDD - Introduction and example (v. simple) - Quick intro to JUnit 3. No object is an island - robot example or similar (using mocks) 4. Expectations Part II: working with 3rd party software 5. Servlet example - extended example - comparison with Cactus 6. DB Examples Part III: GUIs 7. GUI examples 8. Part IV: Living with Unit testing 9. Test organisation 10. Test smells 11. Retrofitting Part V: Other environments 12. C++ (with Workshare?) 13. bash 14. Dynamic & metaprogramming Part VI: Closing 16. More Test Patterns. 15. Summing up Appendices A. Tools B. Resources C. Diagram conventions |
From: Nat P. <np...@us...> - 2002-08-09 18:46:04
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv22398/doc/xdocs Modified Files: _template.xml doc-book.xml Log Message: Changes to build on Linux + added chapters to book Index: _template.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/_template.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- _template.xml 7 Aug 2002 21:52:44 -0000 1.2 +++ _template.xml 9 Aug 2002 18:46:01 -0000 1.3 @@ -1,8 +1,8 @@ <?xml version="1.0"?> <!DOCTYPE article [ - <!ENTITY % docbook SYSTEM "file:///@docpath@/dtd/docbookx.dtd"> - <!ENTITY % extra_entities SYSTEM "file:///@docpath@/extra_entities.xml"> - <!ENTITY @fragment.name@ SYSTEM "file:///@docpath@/@fragment.name@.xml"> + <!ENTITY % docbook SYSTEM "file:@docpath@/dtd/docbookx.dtd"> + <!ENTITY % extra_entities SYSTEM "file:@docpath@/extra_entities.xml"> + <!ENTITY @fragment.name@ SYSTEM "file:@docpath@/@fragment.name@.xml"> %docbook; %extra_entities; ]> Index: doc-book.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/doc-book.xml,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- doc-book.xml 9 Aug 2002 17:55:48 -0000 1.3 +++ doc-book.xml 9 Aug 2002 18:46:01 -0000 1.4 @@ -4,6 +4,7 @@ <!ENTITY % extra_entities SYSTEM "file:@docpath@/extra_entities.xml"> <!ENTITY chp_brief_intro SYSTEM "file:@docpath@/brief_introduction.xml"> <!ENTITY testing_guis1 SYSTEM "file:@docpath@/testing_guis_1.xml"> + <!ENTITY chp_random SYSTEM "file:@docpath@/random.xml"> <!ENTITY notes SYSTEM "file:@docpath@/notes.xml"> %docbook; %extra_entities; @@ -28,7 +29,10 @@ <holder>Steve Freeman</holder> </copyright> </bookinfo> - + &testing_guis1; + <part><title>Living With Unit Testing</title> + &chp_random; + </part> ¬es; </book> |
From: Nat P. <np...@us...> - 2002-08-09 18:13:05
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv7678/doc/xdocs Modified Files: random.xml Log Message: Minor changes to wording Index: random.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/random.xml,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- random.xml 8 Aug 2002 15:03:17 -0000 1.3 +++ random.xml 9 Aug 2002 18:13:01 -0000 1.4 @@ -274,7 +274,7 @@ <methodname>testRandomTemperatureRaining</methodname> test failed. Not only that, my <methodname>testRandomTemperatureSunny</methodname> test failed as well! Why did that happen? The behaviour I added to the -<methodname>randomize</methodname> method should not have had an affect +<methodname>randomize</methodname> method should not have had an effect when it was not raining. </para> @@ -286,7 +286,10 @@ <para>Actually, looking at it again, I realise that I swapped the order of that statements that randomized the rain and temperature. The tests now initialise the stream of mock random numbers in the wrong -order. This is not good: tests should not be tied to the internal +order. This should not matter: the numbers are random so the order +in which they are read from the stream doesn't matter. But my tests +specify that it does matter. +This is not good: tests should not be tied to the internal implementation details of the class. They should specify only its externally visible behaviour. How can I make my tests less brittle? </para> |
From: Nat P. <np...@us...> - 2002-08-09 17:55:52
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv32483/doc/xdocs Modified Files: doc-book.xml extra_entities.xml htmlbook.xsl Log Message: Modified paths so that the book can build on Linux Index: doc-book.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/doc-book.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- doc-book.xml 5 Aug 2002 01:30:26 -0000 1.2 +++ doc-book.xml 9 Aug 2002 17:55:48 -0000 1.3 @@ -1,10 +1,10 @@ <?xml version="1.0"?> <!DOCTYPE book [ - <!ENTITY % docbook SYSTEM "file:///@docpath@/dtd/docbookx.dtd"> - <!ENTITY % extra_entities SYSTEM "file:///@docpath@/extra_entities.xml"> - <!ENTITY chp_brief_intro SYSTEM "file:///@docpath@/brief_introduction.xml"> - <!ENTITY testing_guis1 SYSTEM "file:///@docpath@/testing_guis_1.xml"> - <!ENTITY notes SYSTEM "file:///@docpath@/notes.xml"> + <!ENTITY % docbook SYSTEM "file://@docpath@/dtd/docbookx.dtd"> + <!ENTITY % extra_entities SYSTEM "file:@docpath@/extra_entities.xml"> + <!ENTITY chp_brief_intro SYSTEM "file:@docpath@/brief_introduction.xml"> + <!ENTITY testing_guis1 SYSTEM "file:@docpath@/testing_guis_1.xml"> + <!ENTITY notes SYSTEM "file:@docpath@/notes.xml"> %docbook; %extra_entities; ]> Index: extra_entities.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/extra_entities.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- extra_entities.xml 5 Aug 2002 01:29:49 -0000 1.2 +++ extra_entities.xml 9 Aug 2002 17:55:48 -0000 1.3 @@ -1,2 +1,2 @@ -<!ENTITY redbar SYSTEM "file:///@docpath@/red_bar.xml"> -<!ENTITY greenbar SYSTEM "file:///@docpath@/green_bar.xml"> +<!ENTITY redbar SYSTEM "file:@docpath@/red_bar.xml"> +<!ENTITY greenbar SYSTEM "file:@docpath@/green_bar.xml"> Index: htmlbook.xsl =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.xsl,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- htmlbook.xsl 5 Aug 2002 01:29:21 -0000 1.4 +++ htmlbook.xsl 9 Aug 2002 17:55:48 -0000 1.5 @@ -2,7 +2,7 @@ <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> - <xsl:import href="file:///@xslpath@/html/docbook.xsl"/> + <xsl:import href="file:@xslpath@/html/docbook.xsl"/> <xsl:param name="use.extensions" select="'1'"/> <xsl:param name="html.stylesheet" select="'htmlbook.css'"/> |
From: Nat P. <np...@us...> - 2002-08-08 15:03:22
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv1957 Modified Files: random.xml Log Message: Merged Steve's changes. Rewrote some text, finished Summary section. Index: random.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/random.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- random.xml 7 Aug 2002 21:53:44 -0000 1.2 +++ random.xml 8 Aug 2002 15:03:17 -0000 1.3 @@ -1,159 +1,164 @@ -<chapter> - <title>Random Acts</title> +<chapter status="draft"> +<title>Random Acts</title> - <section> - <title>Introduction</title> - <remark>Expand this</remark> - - <para> - Pseudo-random behaviour is used in many applications. - In games it is used to portray natural behaviours that are too complex to - simulate accurately, or to add variety to behaviours that are too predictable - when simulated algorithmically. - </para> - - <para> - How do we test randomness? - </para> - - </section> <!-- Introduction --> - - <section> - <title>Come Rain...</title> - - <para> - I have been contracted to write a game that simulates the strategy of - Formula One motor racing. - My customer tells me that the weather plays has an important effect on - F1 strategy, so my game will have to simulate the weather somehow. - However, she wants the game to run in a web browser, and that means - writing it as a Java applet. - I'm going to rule out a realistic weather simulation — I'm going to - need all those CPU cycles to simulate the cars and drivers — and - instead randomly generate weather effects. - </para> - - <para> - The first story I have is that the player must choose different tyres - depending on whether it is raining or not. Most of the time it will - be sunny but it should rain on every one out of every five races, - chosen at random. - Time to write a test, but how do I test a random event? - The weather object is obviously going to use a random number generator - to test the probability of rain. If that random number generator is - completely encapsulated within the weather object, I cannot override - the randomness to cause the behaviour I want to test. - Therefore, I need to mock that random number generator in my tests to - feed in fixed values, and pass the random number generator to the - weather object's constructor. - </para> - - <programlisting lang="java"> -public void testRandomRain() { - MockRandom rng = new MockRandom(); - - Weather weather = new Weather( rng ); - - rng.setNextDouble( 0.0 ); - weather.randomize(); - assertTrue( "is raining", weather.isRaining() ); - - rng.setNextDouble( Weather.CHANCE_OF_RAIN ); - weather.randomize(); - assertTrue( "is not raining", !weather.isRaining() ); - - rng.setNextDouble( 1.0 ); - weather.randomize(); - assertTrue( "is not raining", !weather.isRaining() ); -}</programlisting> - - <para> - Here's the <classname>MockRandom</classname> class used by the test: - </para> +<chapterinfo> + <author>Nat Pryce</author> + <copyright><year>2002</year> <holder>Nat Pryce</holder></copyright> +</chapterinfo> + +<section> +<title>Introduction</title> + +<remark>Expand this</remark> + +<para> +Pseudo-random behaviour is used in many applications. +In games it is used to portray natural behaviours that are too complex to +simulate accurately, or to add variety to behaviours that are too predictable +when simulated algorithmically. +</para> + +<para> +How do we test randomness? +</para> + +</section> + +<section> +<title>Come Rain...</title> + +<para> +I have been contracted to write a game that simulates the strategy of +Formula One motor racing. +My customer tells me that the weather plays an important effect +in F1 strategy, so my game will have to simulate the weather somehow. +However, she wants the game to run in a web browser, and that +means writing it as a Java applet. +I'm going to rule out a realistic weather simulation — I'm going to +need all those CPU cycles to simulate the cars and drivers — and +instead randomly generate weather effects. +</para> + +<para> +The first story I have is that the player must choose different tyres +depending on whether it is raining or not. Most of the time it will +be sunny but it should rain on every one out of every five races, +chosen at random. +Time to write a test, but how do I test a random event? +The weather object is obviously going to use a random number generator +to test the probability of rain. If that random number generator is +completely encapsulated within the weather object, I cannot override +the randomness to cause the behaviour I want to test. +Therefore, I need to mock that random number generator in my tests to +feed in fixed values, and pass the random number generator to the +weather object's constructor. +</para> - <programlisting> -public class MockRandom extends Random +<programlisting lang="java">public void testRandomRain() { + MockRandom rng = new MockRandom(); + + Weather weather = new Weather( rng ); + + rng.setNextDouble( 0.0 ); + weather.randomize(); + assertTrue( "is raining", weather.isRaining() ); + + rng.setNextDouble( Weather.CHANCE_OF_RAIN ); + weather.randomize(); + assertTrue( "is not raining", !weather.isRaining() ); + + rng.setNextDouble( 1.0 ); + weather.randomize(); + assertTrue( "is not raining", !weather.isRaining() ); +}</programlisting> + +<para> +Here's the <classname>MockRandom</classname> class used by the test: +</para> + +<programlisting>public class MockRandom + extends Random { private double nextDouble = 0.0; - + public void setNextDouble( double d ) { nextDouble = d; } - + public double nextDouble() { return nextDouble; } -}</programlisting> +} +</programlisting> - <para> - And now we can write a Weather class that passes the tests: - </para> +<para> +And now we can write a Weather class that passes the tests: +</para> - <programlisting lang="java"> -public class Weather +<programlisting lang="java">public class Weather { public static double CHANCE_OF_RAIN = 0.2; - - + + private Random rng; private boolean isRaining = false; - + public Weather( Random rng ) { this.rng = rng; } - + public boolean isRaining() { return isRaining; } - + public void randomize() { isRaining = rng.nextDouble() < CHANCE_OF_RAIN; } }</programlisting> - </section> <!-- Come rain --> +</section> + - <section> - <title>...Or Shine</title> +<section> +<title>...Or Shine</title> - <para> - My customer now tells me that ground temperature is also - important to race strategy. - The player should choose different tyre compounds depending on the - temperature. Now my weather object must choose a temperature and whether - it is raining, both at random. - The random temperature will be chosen from a range of 20°C and 30°C. - But, the ground should be on average half the temperature when it is raining - compared to when it is sunny. - </para> - - <para> - Again, let's write a test. Actually, I think we need - two tests, one to test that the temperature - is selected from within the forecast range when it is sunny, - and another to test that it is half the sunny temperature when raining. - Let's start with the former. Again we need to mock the random number - generator, but this time the <methodname>randomize</methodname> method - will get <emphasis>two</emphasis> random numbers. We need to change our - <classname>MockRandom</classname> class to mock a stream of random - numbers, rather than just one: - </para> +<para>My customer now tells me that ground temperature is also +important to race strategy. +The player should choose different tyre compounds depending on the +temperature. Now my weather object must choose a temperature and whether +it is raining, both at random. +The random temperature will be chosen from a range of 20°C and 30°C. +But, the ground should be on average half the temperature when it is raining +compared to when it is sunny. +</para> + +<para> +Again, let's write a test. Actually, I think we need +two tests, one to test that the temperature +is selected from within the forecast range when it is sunny, +and another to test that it is half the sunny temperature when raining. +Let's start with the former. Again we need to mock the random number +generator, but this time the <methodname>randomize</methodname> method +will get <emphasis>two</emphasis> random numbers. We need to change our +<classname>MockRandom</classname> class to mock a stream of random +numbers, rather than just one: +</para> - <programlisting> -public class MockRandom extends Random +<programlisting>public class MockRandom + extends Random { private double[] nextDoubles = {0.0}; private int nextIndex = 0; - + public void setNextDouble( double d ) { setNextDoubles( new double[]{ d } ); } - + public void setNextDoubles( double[] d ) { nextDoubles = d; nextIndex = 0; } - + public double nextDouble() { double result = nextDoubles[nextIndex]; nextIndex = (nextIndex + 1) % nextDoubles.length; @@ -161,19 +166,18 @@ } }</programlisting> - <para> - Ok, the new <classname>MockRandom</classname> doesn't affect our existing - tests, so we can go on to write the test for random temperature when sunny: - </para> +<para> +Ok, the new <classname>MockRandom</classname> doesn't affect our existing +tests, so we can go on to write the test for random temperature when sunny: +</para> - <programlisting> -public void testRandomTemperatureSunny() { +<programlisting>public void testRandomTemperatureSunny() { MockRandom rng = new MockRandom(); final double SUNNY = 1.0; - + Weather weather = new Weather( rng ); - + rng.setNextDoubles( new double[] { SUNNY, 0.0 } ); weather.randomize(); assertEquals( "should be min temperature", @@ -191,58 +195,56 @@ Weather.MAX_TEMPERATURE, weather.getTemperature(), 0.0 ); }</programlisting> - <para> - And write code to pass that test: - </para> +<para> +And write code to pass that test: +</para> - <programlisting> -public class Weather +<programlisting>public class Weather { public static double CHANCE_OF_RAIN = 0.2; <emphasis>public static double MIN_TEMPERATURE = 20; public static double MAX_TEMPERATURE = 30;</emphasis> - + private Random rng; private boolean isRaining = false; <emphasis>private double temperature = MIN_TEMPERATURE;</emphasis> - - public Weather( Random aRng ) { - rng = aRng; + + public Weather( Random rng ) { + this.rng = rng; } - + public boolean isRaining() { return isRaining; } - + <emphasis>public double getTemperature() { return temperature; }</emphasis> - + public void randomize() { isRaining = rng.nextDouble() < CHANCE_OF_RAIN; - <emphasis>temperature = MIN_TEMPERATURE + + <emphasis>temperature = MIN_TEMPERATURE + rng.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE);</emphasis> } }</programlisting> - <para> - Now for the temperature when it is raining. The test will look very similar - the the one I just wrote, except that it expect the temperatures to be - half those when sunny. - </para> +<para> +Now for the temperature when it is raining. The test will look very similar +the the one I just wrote, except that it expect the temperatures to be +half those when sunny. +</para> - <programlisting> -public void testRandomTemperatureRaining() { +<programlisting>public void testRandomTemperatureRaining() { MockRandom rng = new MockRandom(); final double RAIN = 0.0; - + Weather weather = new Weather( rng ); - + rng.setNextDoubles( new double[] { RAIN, 0.0 } ); weather.randomize(); assertEquals( "should be min rainy temperature", Weather.MIN_TEMPERATURE/2, weather.getTemperature(), 0.0 ); - + rng.setNextDoubles( new double[] { RAIN, 0.5 } ); weather.randomize(); assertEquals( "should be average rainy temperature", @@ -255,67 +257,63 @@ Weather.MAX_TEMPERATURE/2, weather.getTemperature(), 0.0 ); }</programlisting> - <para> - Now I'll change the <classname>Weather</classname>'s - <methodname>randomize</methodname> to half the temperature when it is raining: - </para> - - <programlisting> -public void randomize() { - temperature = MIN_TEMPERATURE + - rng.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE); +<para> +Now I'll change the <classname>Weather</classname>'s +<methodname>randomize</methodname> to half the temperature when it is raining: +</para> +<programlisting>public void randomize() { + temperature = MIN_TEMPERATURE + + rng.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE); + isRaining = rng.nextDouble() < CHANCE_OF_RAIN; if( isRaining ) temperature *= 0.5; }</programlisting> - <para> - That was easy! I'll just run my tests and... whoops! The - <methodname>testRandomTemperatureRaining</methodname> test failed. - Not only that, my <methodname>testRandomTemperatureSunny</methodname> test - failed as well! Why did that happen? The behaviour I added to the - <methodname>randomize</methodname> method should not have had an affect - when it was not raining. - </para> - - </section> <!-- Come shine --> - - <section> - <title>Test Smell: Order Shouldn't Matter</title> - - <para> - Actually, looking at it again, I realise that I swapped the order - of that statements that randomized the rain and temperature. - The tests now initialise the stream of mock random numbers in the wrong - order. This is not good: tests should not be tied to the internal - implementation details of the class. They should specify only its externally - visible behaviour. How can I make my tests less brittle? - </para> - - <para> - There should not be any externally visible dependency between randomising - the temperature and randomising the rain. A way to remove the dependency - is to pass <emphasis>two</emphasis> random number generators to the - <classname>Weather</classname> class, one for the temperature and one for - the rain. My tests can then mock each generator independently to force - a particular outcome, no matter what order the <classname>Weather</classname> - samples the generators. Here's the last test rewritten with two generators: - </para> +<para>That was easy! I'll just run my tests and... whoops! The +<methodname>testRandomTemperatureRaining</methodname> test failed. +Not only that, my <methodname>testRandomTemperatureSunny</methodname> test +failed as well! Why did that happen? The behaviour I added to the +<methodname>randomize</methodname> method should not have had an affect +when it was not raining. +</para> + +</section> + +<section> +<title>Test Smell: Order Shouldn't Matter</title> + +<para>Actually, looking at it again, I realise that I swapped the order +of that statements that randomized the rain and temperature. +The tests now initialise the stream of mock random numbers in the wrong +order. This is not good: tests should not be tied to the internal +implementation details of the class. They should specify only its externally +visible behaviour. How can I make my tests less brittle? +</para> + +<para> +There should not be any externally visible dependency between randomising +the temperature and randomising the rain. A way to remove the dependency +is to pass <emphasis>two</emphasis> random number generators to the +<classname>Weather</classname> class, one for the temperature and one for +the rain. My tests can then mock each generator independently to force +a particular outcome, no matter what order the <classname>Weather</classname> +samples the generators. Here's the last test rewritten with two generators: +</para> - <programlisting> -public void testRandomTemperatureRaining() { +<programlisting>public void testRandomTemperatureRaining() { <emphasis>MockRandom rain_rng = new MockRandom(); rain_rng.setNextDouble(0.0); - + MockRandom temp_rng = new MockRandom(); - + Weather weather = new Weather( rain_rng, temp_rng ); - + temp_rng.setNextDouble( 0.0 );</emphasis> weather.randomize(); assertEquals( "should be min rainy temperature", Weather.MIN_TEMPERATURE/2, weather.getTemperature(), 0.0 ); - + <emphasis>temp_rng.setNextDouble( 0.5 );</emphasis> weather.randomize(); assertEquals( "should be average rainy temperature", @@ -328,162 +326,160 @@ Weather.MAX_TEMPERATURE/2, weather.getTemperature(), 0.0 ); }</programlisting> - <para> - I can rewrite the <methodname>testRandomTemperatureSunny</methodname> - test in a similar way and I also have to change the - <methodname>testRandomRain</methodname> test to instantiate the - <classname>Weather</classname> object with two random number generators; - we'll skip over the code to save trees. - </para> - - <para> - Now I have failing tests for the behaviour I want to implement, so I - need to change my Weather class to use two random number generators: - </para> +<para> +I can rewrite the <methodname>testRandomTemperatureSunny</methodname> +test in a similar way and I also have to change the +<methodname>testRandomRain</methodname> test to instantiate the +<classname>Weather</classname> object with two random number generators; +we'll skip over the code to save trees. +</para> + +<para> +Now I have failing tests for the behaviour I want to implement, so I +need to change my Weather class to use two random number generators: +</para> - <programlisting> -public class Weather +<programlisting>public class Weather { public static double CHANCE_OF_RAIN = 0.2; - public static double MIN_TEMPERATURE = 20; // degrees C - public static double MAX_TEMPERATURE = 30; // degrees C - + public static double MIN_TEMPERATURE = 20; + public static double MAX_TEMPERATURE = 30; + <emphasis>private Random tempRandom, rainRandom;</emphasis> private boolean isRaining = false; private double temperature = MIN_TEMPERATURE; - + <emphasis>public Weather( Random rainRandom, Random tempRandom ) { this.tempRandom = tempRandom; this.rainRandom = rainRandom; }</emphasis> - + [...] - + public void randomize() { - temperature = MIN_TEMPERATURE + + temperature = MIN_TEMPERATURE + <emphasis>tempRandom</emphasis>.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE); - + isRaining = <emphasis>rainRandom</emphasis>.nextDouble() < CHANCE_OF_RAIN; if( isRaining ) temperature *= 0.5; } }</programlisting> - <tip> - Only test the order in which an object calls methods of other objects - if that is an important aspect of your object's publically visible - behaviour. If it is unimportant, your tests will be brittle if your - they expect one particular order. - </tip> - - <para> - Finally, my <classname>MockRandom</classname> class now contains behaviour - that I don't use, and that I've realised is a bad idea. I'll discard - that code by restoring the original version of the class from my source - code repository. - </para> - - </section> <!-- Test Smell --> - - <section> - <title>Refactoring: Too Many Arguments</title> - <para> - My customer is happy. However, she tells me, in Formula One, teams - use tyres with different treads depending on how wet the track is, so - our weather class needs to simulate both if it is raining, and - how wet the track is. Another important element of Formula One strategy - is changing tyres when the weather changes, so the rain should start and - stop at random intervals, and the track should become wetter when it is - raining and dry off when it is sunny. - </para> - - <para> - All this is straightforward to implement, but my nose is twitching: - my code, although functional, is smelly. - I can foresee that as I add functionality to the - <classname>Weather</classname> class, the implementation will become - increasingly awkward because there will be too many random number generators. - In particular each time I add more random behaviour I will have to change - all the tests because the signature of the constructor will have changed, - and the constructor will end up with far too many parameters: - </para> +<tip> +Only test the order in which an object calls methods of other objects +if that is an important aspect of your object's publically visible +behaviour. If it is unimportant, your tests will be brittle if your +they expect one particular order. +</tip> + +<para> +Finally, my <classname>MockRandom</classname> class now contains behaviour +that I don't use, and that I've realised is a bad idea. I'll discard +that code by restoring the original version of the class from my source +code repository. +</para> + +</section> + + +<section> +<title>Refactoring: Too Many Arguments</title> +<para> +My customer is happy with progress so far but tells me that Formula One +teams use tyres with different treads depending on how wet the track is. +She wants the player to have to make the same decisions so my weather class +will have to randomise how heavily the rain is falling. +Another important element of Formula One strategy is changing tyres when +the weather changes, so the rain should start and stop at random intervals, +and the track should become wetter when it is raining and dry off when it +is sunny. +</para> + +<para> +All this is straightforward to implement, but my nose is twitching: +my code, although functional, is smelly. +I can foresee that as I add functionality to the +<classname>Weather</classname> class, the implementation will become +increasingly awkward because I will have to pass in more random number +generators. +Each time I add more random behaviour I will have to change +all the tests because the signature of the constructor will have changed, +and the constructor will end up with far too many parameters: +</para> - <programlisting> -public Weather( Random rainRandom, Random tempRandom, Random wetnessRandom, +<programlisting>public Weather( Random rainRandom, Random tempRandom, Random wetnessRandom, Random rainDurationRandom, Random dryDurationRandom ) { [...] }</programlisting> - <para> - I recognise this "code smell". Every time I see a method that takes a lot - of arguments, I know that there is a new concept waiting to be extracted. - Just as the method - <methodname>drawRectangle( int x, int y, int width, int height )</methodname> - indicates that a Rectangle class should be factored out and the method - replaced by <methodname>draw( Rectangle r )</methodname>, so all those - <classname>Random</classname> arguments indicate that there is an - weather-specific source of randomness waiting to be extracted. - It's time to don my refactoring hat and clear up this mess now; - leaving it any later will just make more work. The first thing I need - to do is define an interface for a weather-specific source of randomness: - </para> +<para> +I recognise this "code smell". Every time I see a method that takes a lot +of arguments, I know that there is a new concept waiting to be extracted. +Just as the method +<methodname>drawRectangle( int x, int y, int width, int height )</methodname> +indicates that a Rectangle class should be factored out and the method +replaced by <methodname>draw( Rectangle r )</methodname>, so all those +<classname>Random</classname> arguments indicate that there is a +weather-specific source of randomness waiting to be extracted. +It's time to don my refactoring hat and clear up this mess now; +leaving it any later will just make more work. The first thing I need +to do is define an interface for a weather-specific source of randomness: +</para> - <programlisting> -public interface WeatherRandom +<programlisting>public interface WeatherRandom { boolean nextIsRaining(); double nextTemperature(); }</programlisting> - <para> - I don't have to implement this interface right now. - I can test the <classname>Weather</classname> class by mocking the interface, - and then test and write an implementation once the - <classname>Weather</classname> tests are passing. - Our new <classname>WeatherRandom</classname> class simplifies our tests - greatly. We no longer have to test that the <classname>Weather</classname> - class compares probabilities against random numbers correctly; that will - be the responsibility of the real implementation of - <classname>WeatherRandom</classname>. Instead we just have to test that - the <classname>Weather</classname> class stores the random weather and - calculates a cooler temperature when it is raining. Here's our - <methodname>testRandomTemperatureRaining</methodname> test: - </para> +<para> +I don't have to implement this interface right now. +I can test the <classname>Weather</classname> class by mocking the interface, +and then test and write an implementation once the +<classname>Weather</classname> tests are passing. +Our new <classname>WeatherRandom</classname> class simplifies our tests +greatly. We no longer have to test that the <classname>Weather</classname> +class compares probabilities against random numbers correctly; that will +be the responsibility of the real implementation of +<classname>WeatherRandom</classname>. Instead we just have to test that +the <classname>Weather</classname> class stores the random weather and +calculates a cooler temperature when it is raining. Here's our +<methodname>testRandomTemperatureRaining</methodname> test: +</para> - <programlisting> -public void testRandomTemperatureRaining() { +<programlisting>public void testRandomTemperatureRaining() { final double TEMPERATURE = 20; - + MockWeatherRandom rng = new MockWeatherRandom() { public boolean nextIsRaining() { return true; } public double nextTemperature() { return TEMPERATURE; } }; - + Weather weather = new Weather( rng ); - + weather.randomize(); - assertEquals( "temperature", + assertEquals( "temperature", TEMPERATURE/2.0, weather.getTemperature(), 0.0 ); }</programlisting> - <para> - Now I have to modify the <classname>Weather</classname> class to - use a <classname>WeatherRandom</classname> to pass the tests: - </para> +<para> +Now I have to modify the <classname>Weather</classname> class to +use a <classname>WeatherRandom</classname> for it to pass the tests: +</para> - <programlisting> -public class Weather +<programlisting>public class Weather { <emphasis>private WeatherRandom random;</emphasis> private boolean isRaining = false; private double temperature = 0.0; - + <emphasis>public Weather( WeatherRandom random ) { - this.random= random; + this.random = random; }</emphasis> - + [...] - + public void randomize() { <emphasis>temperature = random.nextTemperature(); isRaining = random.nextIsRaining();</emphasis> @@ -491,22 +487,21 @@ } }</programlisting> - <para> - And finally I need to test and implement a real - <classname>WeatherRandom</classname> that generates random weather - using a random number generator. - Each of the <classname>WeatherRandom</classname> methods can be tested - individually using a MockRandom object, just as we did in our earlier - tests of the <classname>Weather</classname> class. Because each method - makes one call to the random number generator, no internal - implementation details leak out into our tests. - </para> +<para> +And finally I need to test and implement a real +<classname>WeatherRandom</classname> that generates random weather +using a random number generator. +Each of the <classname>WeatherRandom</classname> methods can be tested +individually using a MockRandom object, just as we did in our earlier +tests of the <classname>Weather</classname> class. Because each method +makes one call to the random number generator, no internal +implementation details leak out into our tests. +</para> - <programlisting> -public void testNextIsRaining() { +<programlisting>public void testNextIsRaining() { MockRandom rng = new MockRandom(); WeatherRandom weather_random = new DefaultWeatherRandom(rng); - + rng.setNextDouble( 0.0 ); assertTrue( "is raining", weather_random.nextIsRaining() ); @@ -517,16 +512,16 @@ assertTrue( "is not raining", !weather_random.nextIsRaining() ); }</programlisting> - <para> - I can now go on to implement the additional random behaviour requested - by my customer. I will define each additional random effect as a method - in the <classname>WeatherRandom</classname> interface. I will also define - a sensible default result in the <classname>MockWeatherRandom</classname> - so that tests that do not care about the effect do not have to be changed. - </para> +<para> +I can now go on to implement the additional random behaviour requested +by my customer. I will define each additional random effect as a method +in the <classname>WeatherRandom</classname> interface. I will also define +a sensible default result in the <classname>MockWeatherRandom</classname> +so that tests that do not care about the effect do not have to be changed. +</para> - <programlisting> -public interface WeatherRandom +<programlisting> +public interface WeatherRandom { boolean nextIsRaining(); double nextTemperature(); @@ -535,34 +530,42 @@ double nextDryDuration();</emphasis> }</programlisting> - </section> <!-- Too many arguments --> - - <section> - <title>What Have We Learned?</title> +</section> - <remark>Expand this</remark> +<section> +<title>What Have We Learned?</title> - <para> - Test random behaviour by pulling the random number generator out of the - object being tested and mocking it. - </para> - - <para> - Mocking random behaviour can expose implementation details by assuming - how individual elements of a random number sequence will be used by - the class under test. This breaks encapsulation and makes tests brittle. - Avoid brittle tests by "parallelizing" your random number streams. - Initialise your objects with multiple sources of randomness that can be - mocked independently to test specific behaviours. - </para> - - <para> - Define application-specific random generators, rather than passing multiple - random number generators into your class, so that changes to the random - behaviour of your class do not cause changes to ripple through your class - and all of its tests. - </para> +<para> +Hopefully this chapter has shown you that it is possible to test random +behaviour without referring to your old college statistics text books. +Random behaviour can be divided into two parts, the part that generates +randomness, and the part that acts upon that randomness. By dividing the +two parts into separate objects and defining a clean interface between +the two, you can mock the randomness and feed deterministic values into +the object you want to test. +</para> + +<para> +However, as you have seen, mocking random behaviour can expose implementation +details when tests contain assumptions about how the elements of a random +number sequence are used by the class being tested. +These assumptions break encapsulation and make the tests brittle. +You can avoid brittle tests by "parallelizing" your random number streams: +initialise your objects with multiple sources of randomness that can be +mocked independently to test specific behaviours. +</para> + +<para> +Parallelizing the sources of randomness by passing an object multiple +independent random number streams can also make your tests brittle. +Changes to the internal behaviour of the class will require changes +to the class' public interface. This brittleness can be avoided by +replacing multiple random number generators with application specific +sources of randomness. Changes to the random behaviour of your +class are limited to the application specific random generator and do +not cause a cascade of changes to your class interface and all of its tests. +</para> - </section> <!-- what have we learned --> +</section> </chapter> |
From: Steve F. <sm...@us...> - 2002-08-07 21:53:47
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv11406/doc/xdocs Modified Files: random.xml Log Message: added Nat's random chapter Index: random.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/random.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- random.xml 7 Aug 2002 17:21:52 -0000 1.1 +++ random.xml 7 Aug 2002 21:53:44 -0000 1.2 @@ -1,161 +1,159 @@ -<?xml version="1.0"?> - <chapter> -<title>Random Acts</title> - -<section> -<title>Introduction</title> - -<remark>Expand this</remark> - -<para> -Pseudo-random behaviour is used in many applications. -In games it is used to portray natural behaviours that are too complex to -simulate accurately, or to add variety to behaviours that are too predictable -when simulated algorithmically. -</para> - -<para> -How do we test randomness? -</para> - -</section> - -<section> -<title>Come Rain...</title> - -<para> -I have been contracted to write a game that simulates the strategy of -Formula One motor racing. -My customer tells me that the weather plays has an important effect on -F1 strategy, so my game will have to simulate the weather somehow. -However, she wants the game to run in a web browser, and that means -writing it as a Java applet. -I'm going to rule out a realistic weather simulation — I'm going to -need all those CPU cycles to simulate the cars and drivers — and -instead randomly generate weather effects. -</para> - -<para> -The first story I have is that the player must choose different tyres -depending on whether it is raining or not. Most of the time it will -be sunny but it should rain on every one out of every five races, -chosen at random. -Time to write a test, but how do I test a random event? -The weather object is obviously going to use a random number generator -to test the probability of rain. If that random number generator is -completely encapsulated within the weather object, I cannot override -the randomness to cause the behaviour I want to test. -Therefore, I need to mock that random number generator in my tests to -feed in fixed values, and pass the random number generator to the -weather object's constructor. -</para> - -<programlisting lang="java">public void testRandomRain() { - MockRandom rng = new MockRandom(); - - Weather weather = new Weather( rng ); - - rng.setNextDouble( 0.0 ); - weather.randomize(); - assertTrue( "is raining", weather.isRaining() ); - - rng.setNextDouble( Weather.CHANCE_OF_RAIN ); - weather.randomize(); - assertTrue( "is not raining", !weather.isRaining() ); - - rng.setNextDouble( 1.0 ); - weather.randomize(); - assertTrue( "is not raining", !weather.isRaining() ); -}</programlisting> + <title>Random Acts</title> -<para> -Here's the <classname>MockRandom</classname> class used by the test: -</para> + <section> + <title>Introduction</title> + <remark>Expand this</remark> + + <para> + Pseudo-random behaviour is used in many applications. + In games it is used to portray natural behaviours that are too complex to + simulate accurately, or to add variety to behaviours that are too predictable + when simulated algorithmically. + </para> + + <para> + How do we test randomness? + </para> + + </section> <!-- Introduction --> + + <section> + <title>Come Rain...</title> + + <para> + I have been contracted to write a game that simulates the strategy of + Formula One motor racing. + My customer tells me that the weather plays has an important effect on + F1 strategy, so my game will have to simulate the weather somehow. + However, she wants the game to run in a web browser, and that means + writing it as a Java applet. + I'm going to rule out a realistic weather simulation — I'm going to + need all those CPU cycles to simulate the cars and drivers — and + instead randomly generate weather effects. + </para> + + <para> + The first story I have is that the player must choose different tyres + depending on whether it is raining or not. Most of the time it will + be sunny but it should rain on every one out of every five races, + chosen at random. + Time to write a test, but how do I test a random event? + The weather object is obviously going to use a random number generator + to test the probability of rain. If that random number generator is + completely encapsulated within the weather object, I cannot override + the randomness to cause the behaviour I want to test. + Therefore, I need to mock that random number generator in my tests to + feed in fixed values, and pass the random number generator to the + weather object's constructor. + </para> + + <programlisting lang="java"> +public void testRandomRain() { + MockRandom rng = new MockRandom(); + + Weather weather = new Weather( rng ); + + rng.setNextDouble( 0.0 ); + weather.randomize(); + assertTrue( "is raining", weather.isRaining() ); + + rng.setNextDouble( Weather.CHANCE_OF_RAIN ); + weather.randomize(); + assertTrue( "is not raining", !weather.isRaining() ); + + rng.setNextDouble( 1.0 ); + weather.randomize(); + assertTrue( "is not raining", !weather.isRaining() ); +}</programlisting> + + <para> + Here's the <classname>MockRandom</classname> class used by the test: + </para> -<programlisting>public class MockRandom - extends Random + <programlisting> +public class MockRandom extends Random { private double nextDouble = 0.0; - + public void setNextDouble( double d ) { nextDouble = d; } - + public double nextDouble() { return nextDouble; } -} -</programlisting> +}</programlisting> -<para> -And now we can write a Weather class that passes the tests: -</para> + <para> + And now we can write a Weather class that passes the tests: + </para> -<programlisting lang="java">public class Weather + <programlisting lang="java"> +public class Weather { public static double CHANCE_OF_RAIN = 0.2; - - + + private Random rng; private boolean isRaining = false; - + public Weather( Random rng ) { this.rng = rng; } - + public boolean isRaining() { return isRaining; } - + public void randomize() { - isRaining = rng.nextDouble() < CHANCE_OF_RAIN; + isRaining = rng.nextDouble() < CHANCE_OF_RAIN; } }</programlisting> -</section> + </section> <!-- Come rain --> + <section> + <title>...Or Shine</title> -<section> -<title>...Or Shine</title> - -<para>My customer now tells me that ground temperature is also -important to race strategy. -The player should choose different tyre compounds depending on the -temperature. Now my weather object must choose a temperature and whether -it is raining, both at random. -The random temperature will be chosen from a range of 20°C and 30°C. -But, the ground should be on average half the temperature when it is raining -compared to when it is sunny. -</para> - -<para> -Again, let's write a test. Actually, I think we need -two tests, one to test that the temperature -is selected from within the forecast range when it is sunny, -and another to test that it is half the sunny temperature when raining. -Let's start with the former. Again we need to mock the random number -generator, but this time the <methodname>randomize</methodname> method -will get <emphasis>two</emphasis> random numbers. We need to change our -<classname>MockRandom</classname> class to mock a stream of random -numbers, rather than just one: -</para> + <para> + My customer now tells me that ground temperature is also + important to race strategy. + The player should choose different tyre compounds depending on the + temperature. Now my weather object must choose a temperature and whether + it is raining, both at random. + The random temperature will be chosen from a range of 20°C and 30°C. + But, the ground should be on average half the temperature when it is raining + compared to when it is sunny. + </para> + + <para> + Again, let's write a test. Actually, I think we need + two tests, one to test that the temperature + is selected from within the forecast range when it is sunny, + and another to test that it is half the sunny temperature when raining. + Let's start with the former. Again we need to mock the random number + generator, but this time the <methodname>randomize</methodname> method + will get <emphasis>two</emphasis> random numbers. We need to change our + <classname>MockRandom</classname> class to mock a stream of random + numbers, rather than just one: + </para> -<programlisting>public class MockRandom - extends Random + <programlisting> +public class MockRandom extends Random { private double[] nextDoubles = {0.0}; private int nextIndex = 0; - + public void setNextDouble( double d ) { setNextDoubles( new double[]{ d } ); } - + public void setNextDoubles( double[] d ) { nextDoubles = d; nextIndex = 0; } - + public double nextDouble() { double result = nextDoubles[nextIndex]; nextIndex = (nextIndex + 1) % nextDoubles.length; @@ -163,18 +161,19 @@ } }</programlisting> -<para> -Ok, the new <classname>MockRandom</classname> doesn't affect our existing -tests, so we can go on to write the test for random temperature when sunny: -</para> + <para> + Ok, the new <classname>MockRandom</classname> doesn't affect our existing + tests, so we can go on to write the test for random temperature when sunny: + </para> -<programlisting>public void testRandomTemperatureSunny() { + <programlisting> +public void testRandomTemperatureSunny() { MockRandom rng = new MockRandom(); final double SUNNY = 1.0; - + Weather weather = new Weather( rng ); - + rng.setNextDoubles( new double[] { SUNNY, 0.0 } ); weather.randomize(); assertEquals( "should be min temperature", @@ -192,56 +191,58 @@ Weather.MAX_TEMPERATURE, weather.getTemperature(), 0.0 ); }</programlisting> -<para> -And write code to pass that test: -</para> + <para> + And write code to pass that test: + </para> -<programlisting>public class Weather + <programlisting> +public class Weather { public static double CHANCE_OF_RAIN = 0.2; <emphasis>public static double MIN_TEMPERATURE = 20; public static double MAX_TEMPERATURE = 30;</emphasis> - + private Random rng; private boolean isRaining = false; <emphasis>private double temperature = MIN_TEMPERATURE;</emphasis> - - public Weather( Random rng ) { - this.rng = rng; + + public Weather( Random aRng ) { + rng = aRng; } - + public boolean isRaining() { return isRaining; } - + <emphasis>public double getTemperature() { return temperature; }</emphasis> - + public void randomize() { - isRaining = rng.nextDouble() < CHANCE_OF_RAIN; - <emphasis>temperature = MIN_TEMPERATURE + + isRaining = rng.nextDouble() < CHANCE_OF_RAIN; + <emphasis>temperature = MIN_TEMPERATURE + rng.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE);</emphasis> } }</programlisting> -<para> -Now for the temperature when it is raining. The test will look very similar -the the one I just wrote, except that it expect the temperatures to be -half those when sunny. -</para> + <para> + Now for the temperature when it is raining. The test will look very similar + the the one I just wrote, except that it expect the temperatures to be + half those when sunny. + </para> -<programlisting>public void testRandomTemperatureRaining() { + <programlisting> +public void testRandomTemperatureRaining() { MockRandom rng = new MockRandom(); final double RAIN = 0.0; - + Weather weather = new Weather( rng ); - + rng.setNextDoubles( new double[] { RAIN, 0.0 } ); weather.randomize(); assertEquals( "should be min rainy temperature", Weather.MIN_TEMPERATURE/2, weather.getTemperature(), 0.0 ); - + rng.setNextDoubles( new double[] { RAIN, 0.5 } ); weather.randomize(); assertEquals( "should be average rainy temperature", @@ -254,63 +255,67 @@ Weather.MAX_TEMPERATURE/2, weather.getTemperature(), 0.0 ); }</programlisting> -<para> -Now I'll change the <classname>Weather</classname>'s -<methodname>randomize</methodname> to half the temperature when it is raining: -</para> - -<programlisting>public void randomize() { - temperature = MIN_TEMPERATURE + + <para> + Now I'll change the <classname>Weather</classname>'s + <methodname>randomize</methodname> to half the temperature when it is raining: + </para> + + <programlisting> +public void randomize() { + temperature = MIN_TEMPERATURE + rng.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE); - - isRaining = rng.nextDouble() < CHANCE_OF_RAIN; + + isRaining = rng.nextDouble() < CHANCE_OF_RAIN; if( isRaining ) temperature *= 0.5; }</programlisting> -<para>That was easy! I'll just run my tests and... whoops! The -<methodname>testRandomTemperatureRaining</methodname> test failed. -Not only that, my <methodname>testRandomTemperatureSunny</methodname> test -failed as well! Why did that happen? The behaviour I added to the -<methodname>randomize</methodname> method should not have had an affect -when it was not raining. -</para> - -</section> - -<section> -<title>Test Smell: Order Shouldn't Matter</title> - -<para>Actually, looking at it again, I realise that I swapped the order -of that statements that randomized the rain and temperature. -The tests now initialise the stream of mock random numbers in the wrong -order. This is not good: tests should not be tied to the internal -implementation details of the class. They should specify only its externally -visible behaviour. How can I make my tests less brittle? -</para> - -<para> -There should not be any externally visible dependency between randomising -the temperature and randomising the rain. A way to remove the dependency -is to pass <emphasis>two</emphasis> random number generators to the -<classname>Weather</classname> class, one for the temperature and one for -the rain. My tests can then mock each generator independently to force -a particular outcome, no matter what order the <classname>Weather</classname> -samples the generators. Here's the last test rewritten with two generators: -</para> + <para> + That was easy! I'll just run my tests and... whoops! The + <methodname>testRandomTemperatureRaining</methodname> test failed. + Not only that, my <methodname>testRandomTemperatureSunny</methodname> test + failed as well! Why did that happen? The behaviour I added to the + <methodname>randomize</methodname> method should not have had an affect + when it was not raining. + </para> + + </section> <!-- Come shine --> + + <section> + <title>Test Smell: Order Shouldn't Matter</title> + + <para> + Actually, looking at it again, I realise that I swapped the order + of that statements that randomized the rain and temperature. + The tests now initialise the stream of mock random numbers in the wrong + order. This is not good: tests should not be tied to the internal + implementation details of the class. They should specify only its externally + visible behaviour. How can I make my tests less brittle? + </para> + + <para> + There should not be any externally visible dependency between randomising + the temperature and randomising the rain. A way to remove the dependency + is to pass <emphasis>two</emphasis> random number generators to the + <classname>Weather</classname> class, one for the temperature and one for + the rain. My tests can then mock each generator independently to force + a particular outcome, no matter what order the <classname>Weather</classname> + samples the generators. Here's the last test rewritten with two generators: + </para> -<programlisting>public void testRandomTemperatureRaining() { + <programlisting> +public void testRandomTemperatureRaining() { <emphasis>MockRandom rain_rng = new MockRandom(); rain_rng.setNextDouble(0.0); - + MockRandom temp_rng = new MockRandom(); - + Weather weather = new Weather( rain_rng, temp_rng ); - + temp_rng.setNextDouble( 0.0 );</emphasis> weather.randomize(); assertEquals( "should be min rainy temperature", Weather.MIN_TEMPERATURE/2, weather.getTemperature(), 0.0 ); - + <emphasis>temp_rng.setNextDouble( 0.5 );</emphasis> weather.randomize(); assertEquals( "should be average rainy temperature", @@ -323,158 +328,162 @@ Weather.MAX_TEMPERATURE/2, weather.getTemperature(), 0.0 ); }</programlisting> -<para> -I can rewrite the <methodname>testRandomTemperatureSunny</methodname> -test in a similar way and I also have to change the -<methodname>testRandomRain</methodname> test to instantiate the -<classname>Weather</classname> object with two random number generators; -we'll skip over the code to save trees. -</para> - -<para> -Now I have failing tests for the behaviour I want to implement, so I -need to change my Weather class to use two random number generators: -</para> + <para> + I can rewrite the <methodname>testRandomTemperatureSunny</methodname> + test in a similar way and I also have to change the + <methodname>testRandomRain</methodname> test to instantiate the + <classname>Weather</classname> object with two random number generators; + we'll skip over the code to save trees. + </para> + + <para> + Now I have failing tests for the behaviour I want to implement, so I + need to change my Weather class to use two random number generators: + </para> -<programlisting>public class Weather + <programlisting> +public class Weather { public static double CHANCE_OF_RAIN = 0.2; public static double MIN_TEMPERATURE = 20; // degrees C public static double MAX_TEMPERATURE = 30; // degrees C - + <emphasis>private Random tempRandom, rainRandom;</emphasis> private boolean isRaining = false; private double temperature = MIN_TEMPERATURE; - + <emphasis>public Weather( Random rainRandom, Random tempRandom ) { this.tempRandom = tempRandom; this.rainRandom = rainRandom; }</emphasis> - + [...] - + public void randomize() { - temperature = MIN_TEMPERATURE + + temperature = MIN_TEMPERATURE + <emphasis>tempRandom</emphasis>.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE); - - isRaining = <emphasis>rainRandom</emphasis>.nextDouble() < CHANCE_OF_RAIN; + + isRaining = <emphasis>rainRandom</emphasis>.nextDouble() < CHANCE_OF_RAIN; if( isRaining ) temperature *= 0.5; } }</programlisting> -<tip> -Only test the order in which an object calls methods of other objects -if that is an important aspect of your object's publically visible -behaviour. If it is unimportant, your tests will be brittle if your -they expect one particular order. -</tip> - -<para> -Finally, my <classname>MockRandom</classname> class now contains behaviour -that I don't use, and that I've realised is a bad idea. I'll discard -that code by restoring the original version of the class from my source -code repository. -</para> - -</section> - - -<section> -<title>Refactoring: Too Many Arguments</title> -<para> -My customer is happy. However, she tells me, in Formula One, teams -use tyres with different treads depending on how wet the track is, so -our weather class needs to simulate both if it is raining, and -how wet the track is. Another important element of Formula One strategy -is changing tyres when the weather changes, so the rain should start and -stop at random intervals, and the track should become wetter when it is -raining and dry off when it is sunny. -</para> - -<para> -All this is straightforward to implement, but my nose is twitching: -my code, although functional, is smelly. -I can foresee that as I add functionality to the -<classname>Weather</classname> class, the implementation will become -increasingly awkward because there will be too many random number generators. -In particular each time I add more random behaviour I will have to change -all the tests because the signature of the constructor will have changed, -and the constructor will end up with far too many parameters: -</para> + <tip> + Only test the order in which an object calls methods of other objects + if that is an important aspect of your object's publically visible + behaviour. If it is unimportant, your tests will be brittle if your + they expect one particular order. + </tip> + + <para> + Finally, my <classname>MockRandom</classname> class now contains behaviour + that I don't use, and that I've realised is a bad idea. I'll discard + that code by restoring the original version of the class from my source + code repository. + </para> + + </section> <!-- Test Smell --> + + <section> + <title>Refactoring: Too Many Arguments</title> + <para> + My customer is happy. However, she tells me, in Formula One, teams + use tyres with different treads depending on how wet the track is, so + our weather class needs to simulate both if it is raining, and + how wet the track is. Another important element of Formula One strategy + is changing tyres when the weather changes, so the rain should start and + stop at random intervals, and the track should become wetter when it is + raining and dry off when it is sunny. + </para> + + <para> + All this is straightforward to implement, but my nose is twitching: + my code, although functional, is smelly. + I can foresee that as I add functionality to the + <classname>Weather</classname> class, the implementation will become + increasingly awkward because there will be too many random number generators. + In particular each time I add more random behaviour I will have to change + all the tests because the signature of the constructor will have changed, + and the constructor will end up with far too many parameters: + </para> -<programlisting>public Weather( Random rainRandom, Random tempRandom, Random wetnessRandom, + <programlisting> +public Weather( Random rainRandom, Random tempRandom, Random wetnessRandom, Random rainDurationRandom, Random dryDurationRandom ) { [...] }</programlisting> -<para> -I recognise this "code smell". Every time I see a method that takes a lot -of arguments, I know that there is a new concept waiting to be extracted. -Just as the method -<methodname>drawRectangle( int x, int y, int width, int height )</methodname> -indicates that a Rectangle class should be factored out and the method -replaced by <methodname>draw( Rectangle r )</methodname>, so all those -<classname>Random</classname> arguments indicate that there is an -weather-specific source of randomness waiting to be extracted. -It's time to don my refactoring hat and clear up this mess now; -leaving it any later will just make more work. The first thing I need -to do is define an interface for a weather-specific source of randomness: -</para> + <para> + I recognise this "code smell". Every time I see a method that takes a lot + of arguments, I know that there is a new concept waiting to be extracted. + Just as the method + <methodname>drawRectangle( int x, int y, int width, int height )</methodname> + indicates that a Rectangle class should be factored out and the method + replaced by <methodname>draw( Rectangle r )</methodname>, so all those + <classname>Random</classname> arguments indicate that there is an + weather-specific source of randomness waiting to be extracted. + It's time to don my refactoring hat and clear up this mess now; + leaving it any later will just make more work. The first thing I need + to do is define an interface for a weather-specific source of randomness: + </para> -<programlisting>public interface WeatherRandom + <programlisting> +public interface WeatherRandom { boolean nextIsRaining(); double nextTemperature(); }</programlisting> -<para> -I don't have to implement this interface right now. -I can test the <classname>Weather</classname> class by mocking the interface, -and then test and write an implementation once the -<classname>Weather</classname> tests are passing. -Our new <classname>WeatherRandom</classname> class simplifies our tests -greatly. We no longer have to test that the <classname>Weather</classname> -class compares probabilities against random numbers correctly; that will -be the responsibility of the real implementation of -<classname>WeatherRandom</classname>. Instead we just have to test that -the <classname>Weather</classname> class stores the random weather and -calculates a cooler temperature when it is raining. Here's our -<methodname>testRandomTemperatureRaining</methodname> test: -</para> + <para> + I don't have to implement this interface right now. + I can test the <classname>Weather</classname> class by mocking the interface, + and then test and write an implementation once the + <classname>Weather</classname> tests are passing. + Our new <classname>WeatherRandom</classname> class simplifies our tests + greatly. We no longer have to test that the <classname>Weather</classname> + class compares probabilities against random numbers correctly; that will + be the responsibility of the real implementation of + <classname>WeatherRandom</classname>. Instead we just have to test that + the <classname>Weather</classname> class stores the random weather and + calculates a cooler temperature when it is raining. Here's our + <methodname>testRandomTemperatureRaining</methodname> test: + </para> -<programlisting>public void testRandomTemperatureRaining() { + <programlisting> +public void testRandomTemperatureRaining() { final double TEMPERATURE = 20; - + MockWeatherRandom rng = new MockWeatherRandom() { public boolean nextIsRaining() { return true; } public double nextTemperature() { return TEMPERATURE; } }; - + Weather weather = new Weather( rng ); - + weather.randomize(); - assertEquals( "temperature", + assertEquals( "temperature", TEMPERATURE/2.0, weather.getTemperature(), 0.0 ); }</programlisting> -<para> -Now I have to modify the <classname>Weather</classname> class to -use a <classname>WeatherRandom</classname> to pass the tests: -</para> + <para> + Now I have to modify the <classname>Weather</classname> class to + use a <classname>WeatherRandom</classname> to pass the tests: + </para> -<programlisting>public class Weather + <programlisting> +public class Weather { <emphasis>private WeatherRandom random;</emphasis> private boolean isRaining = false; private double temperature = 0.0; - + <emphasis>public Weather( WeatherRandom random ) { this.random= random; }</emphasis> - + [...] - + public void randomize() { <emphasis>temperature = random.nextTemperature(); isRaining = random.nextIsRaining();</emphasis> @@ -482,21 +491,22 @@ } }</programlisting> -<para> -And finally I need to test and implement a real -<classname>WeatherRandom</classname> that generates random weather -using a random number generator. -Each of the <classname>WeatherRandom</classname> methods can be tested -individually using a MockRandom object, just as we did in our earlier -tests of the <classname>Weather</classname> class. Because each method -makes one call to the random number generator, no internal -implementation details leak out into our tests. -</para> + <para> + And finally I need to test and implement a real + <classname>WeatherRandom</classname> that generates random weather + using a random number generator. + Each of the <classname>WeatherRandom</classname> methods can be tested + individually using a MockRandom object, just as we did in our earlier + tests of the <classname>Weather</classname> class. Because each method + makes one call to the random number generator, no internal + implementation details leak out into our tests. + </para> -<programlisting>public void testNextIsRaining() { + <programlisting> +public void testNextIsRaining() { MockRandom rng = new MockRandom(); WeatherRandom weather_random = new DefaultWeatherRandom(rng); - + rng.setNextDouble( 0.0 ); assertTrue( "is raining", weather_random.nextIsRaining() ); @@ -507,15 +517,16 @@ assertTrue( "is not raining", !weather_random.nextIsRaining() ); }</programlisting> -<para> -I can now go on to implement the additional random behaviour requested -by my customer. I will define each additional random effect as a method -in the <classname>WeatherRandom</classname> interface. I will also define -a sensible default result in the <classname>MockWeatherRandom</classname> -so that tests that do not care about the effect do not have to be changed. -</para> + <para> + I can now go on to implement the additional random behaviour requested + by my customer. I will define each additional random effect as a method + in the <classname>WeatherRandom</classname> interface. I will also define + a sensible default result in the <classname>MockWeatherRandom</classname> + so that tests that do not care about the effect do not have to be changed. + </para> -<programlisting>public interface WeatherRandom + <programlisting> +public interface WeatherRandom { boolean nextIsRaining(); double nextTemperature(); @@ -524,34 +535,34 @@ double nextDryDuration();</emphasis> }</programlisting> -</section> + </section> <!-- Too many arguments --> -<section> -<title>What Have We Learned?</title> + <section> + <title>What Have We Learned?</title> -<remark>Expand this</remark> + <remark>Expand this</remark> -<para> -Test random behaviour by pulling the random number generator out of the -object being tested and mocking it. -</para> - -<para> -Mocking random behaviour can expose implementation details by assuming -how individual elements of a random number sequence will be used by -the class under test. This breaks encapsulation and makes tests brittle. -Avoid brittle tests by "parallelizing" your random number streams. -Initialise your objects with multiple sources of randomness that can be -mocked independently to test specific behaviours. -</para> - -<para> -Define application-specific random generators, rather than passing multiple -random number generators into your class, so that changes to the random -behaviour of your class do not cause changes to ripple through your class -and all of its tests. -</para> + <para> + Test random behaviour by pulling the random number generator out of the + object being tested and mocking it. + </para> + + <para> + Mocking random behaviour can expose implementation details by assuming + how individual elements of a random number sequence will be used by + the class under test. This breaks encapsulation and makes tests brittle. + Avoid brittle tests by "parallelizing" your random number streams. + Initialise your objects with multiple sources of randomness that can be + mocked independently to test specific behaviours. + </para> + + <para> + Define application-specific random generators, rather than passing multiple + random number generators into your class, so that changes to the random + behaviour of your class do not cause changes to ripple through your class + and all of its tests. + </para> -</section> + </section> <!-- what have we learned --> </chapter> |
From: Steve F. <sm...@us...> - 2002-08-07 21:52:47
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv10800/doc/xdocs Modified Files: _template.xml Log Message: Removed element property from fragment. Everything's an article now. Index: _template.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/_template.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- _template.xml 5 Aug 2002 01:28:29 -0000 1.1 +++ _template.xml 7 Aug 2002 21:52:44 -0000 1.2 @@ -1,5 +1,5 @@ <?xml version="1.0"?> -<!DOCTYPE @element@ [ +<!DOCTYPE article [ <!ENTITY % docbook SYSTEM "file:///@docpath@/dtd/docbookx.dtd"> <!ENTITY % extra_entities SYSTEM "file:///@docpath@/extra_entities.xml"> <!ENTITY @fragment.name@ SYSTEM "file:///@docpath@/@fragment.name@.xml"> |
From: Steve F. <sm...@us...> - 2002-08-07 21:52:31
|
Update of /cvsroot/mockobjects/no-stone-unturned In directory usw-pr-cvs1:/tmp/cvs-serv10617 Modified Files: build.xml Log Message: Removed element property from fragment. Everything's an article now. Index: build.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/build.xml,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- build.xml 5 Aug 2002 01:28:29 -0000 1.3 +++ build.xml 7 Aug 2002 21:52:27 -0000 1.4 @@ -155,7 +155,6 @@ <target name="CheckFragmentProperties"> <fail unless="fragment.name" message="Requires -Dfragment.name=<fragment name>" /> - <fail unless="element" message="Requires -Delement=<element type>" /> </target> <target name="PrepareFragment" |
From: Nat P. <np...@us...> - 2002-08-07 17:21:56
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv26275 Added Files: random.xml Log Message: Added draft of chapter/article about testing random behaviour. --- NEW FILE: random.xml --- <?xml version="1.0"?> <chapter> <title>Random Acts</title> <section> <title>Introduction</title> <remark>Expand this</remark> <para> Pseudo-random behaviour is used in many applications. In games it is used to portray natural behaviours that are too complex to simulate accurately, or to add variety to behaviours that are too predictable when simulated algorithmically. </para> <para> How do we test randomness? </para> </section> <section> <title>Come Rain...</title> <para> I have been contracted to write a game that simulates the strategy of Formula One motor racing. My customer tells me that the weather plays has an important effect on F1 strategy, so my game will have to simulate the weather somehow. However, she wants the game to run in a web browser, and that means writing it as a Java applet. I'm going to rule out a realistic weather simulation — I'm going to need all those CPU cycles to simulate the cars and drivers — and instead randomly generate weather effects. </para> <para> The first story I have is that the player must choose different tyres depending on whether it is raining or not. Most of the time it will be sunny but it should rain on every one out of every five races, chosen at random. Time to write a test, but how do I test a random event? The weather object is obviously going to use a random number generator to test the probability of rain. If that random number generator is completely encapsulated within the weather object, I cannot override the randomness to cause the behaviour I want to test. Therefore, I need to mock that random number generator in my tests to feed in fixed values, and pass the random number generator to the weather object's constructor. </para> <programlisting lang="java">public void testRandomRain() { MockRandom rng = new MockRandom(); Weather weather = new Weather( rng ); rng.setNextDouble( 0.0 ); weather.randomize(); assertTrue( "is raining", weather.isRaining() ); rng.setNextDouble( Weather.CHANCE_OF_RAIN ); weather.randomize(); assertTrue( "is not raining", !weather.isRaining() ); rng.setNextDouble( 1.0 ); weather.randomize(); assertTrue( "is not raining", !weather.isRaining() ); }</programlisting> <para> Here's the <classname>MockRandom</classname> class used by the test: </para> <programlisting>public class MockRandom extends Random { private double nextDouble = 0.0; public void setNextDouble( double d ) { nextDouble = d; } public double nextDouble() { return nextDouble; } } </programlisting> <para> And now we can write a Weather class that passes the tests: </para> <programlisting lang="java">public class Weather { public static double CHANCE_OF_RAIN = 0.2; private Random rng; private boolean isRaining = false; public Weather( Random rng ) { this.rng = rng; } public boolean isRaining() { return isRaining; } public void randomize() { isRaining = rng.nextDouble() < CHANCE_OF_RAIN; } }</programlisting> </section> <section> <title>...Or Shine</title> <para>My customer now tells me that ground temperature is also important to race strategy. The player should choose different tyre compounds depending on the temperature. Now my weather object must choose a temperature and whether it is raining, both at random. The random temperature will be chosen from a range of 20°C and 30°C. But, the ground should be on average half the temperature when it is raining compared to when it is sunny. </para> <para> Again, let's write a test. Actually, I think we need two tests, one to test that the temperature is selected from within the forecast range when it is sunny, and another to test that it is half the sunny temperature when raining. Let's start with the former. Again we need to mock the random number generator, but this time the <methodname>randomize</methodname> method will get <emphasis>two</emphasis> random numbers. We need to change our <classname>MockRandom</classname> class to mock a stream of random numbers, rather than just one: </para> <programlisting>public class MockRandom extends Random { private double[] nextDoubles = {0.0}; private int nextIndex = 0; public void setNextDouble( double d ) { setNextDoubles( new double[]{ d } ); } public void setNextDoubles( double[] d ) { nextDoubles = d; nextIndex = 0; } public double nextDouble() { double result = nextDoubles[nextIndex]; nextIndex = (nextIndex + 1) % nextDoubles.length; return result; } }</programlisting> <para> Ok, the new <classname>MockRandom</classname> doesn't affect our existing tests, so we can go on to write the test for random temperature when sunny: </para> <programlisting>public void testRandomTemperatureSunny() { MockRandom rng = new MockRandom(); final double SUNNY = 1.0; Weather weather = new Weather( rng ); rng.setNextDoubles( new double[] { SUNNY, 0.0 } ); weather.randomize(); assertEquals( "should be min temperature", Weather.MIN_TEMPERATURE, weather.getTemperature(), 0.0 ); rng.setNextDoubles( new double[] { SUNNY, 0.5 } ); weather.randomize(); assertEquals( "should be average temperature", (Weather.MIN_TEMPERATURE + Weather.MAX_TEMPERATURE)/2, weather.getTemperature(), 0.0 ); rng.setNextDoubles( new double[] { SUNNY, 1.0 } ); weather.randomize(); assertEquals( "should be max temperature", Weather.MAX_TEMPERATURE, weather.getTemperature(), 0.0 ); }</programlisting> <para> And write code to pass that test: </para> <programlisting>public class Weather { public static double CHANCE_OF_RAIN = 0.2; <emphasis>public static double MIN_TEMPERATURE = 20; public static double MAX_TEMPERATURE = 30;</emphasis> private Random rng; private boolean isRaining = false; <emphasis>private double temperature = MIN_TEMPERATURE;</emphasis> public Weather( Random rng ) { this.rng = rng; } public boolean isRaining() { return isRaining; } <emphasis>public double getTemperature() { return temperature; }</emphasis> public void randomize() { isRaining = rng.nextDouble() < CHANCE_OF_RAIN; <emphasis>temperature = MIN_TEMPERATURE + rng.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE);</emphasis> } }</programlisting> <para> Now for the temperature when it is raining. The test will look very similar the the one I just wrote, except that it expect the temperatures to be half those when sunny. </para> <programlisting>public void testRandomTemperatureRaining() { MockRandom rng = new MockRandom(); final double RAIN = 0.0; Weather weather = new Weather( rng ); rng.setNextDoubles( new double[] { RAIN, 0.0 } ); weather.randomize(); assertEquals( "should be min rainy temperature", Weather.MIN_TEMPERATURE/2, weather.getTemperature(), 0.0 ); rng.setNextDoubles( new double[] { RAIN, 0.5 } ); weather.randomize(); assertEquals( "should be average rainy temperature", (Weather.MIN_TEMPERATURE + Weather.MAX_TEMPERATURE)/4, weather.getTemperature(), 0.0 ); rng.setNextDoubles( new double[] { RAIN, 1.0 } ); weather.randomize(); assertEquals( "should be max rainy temperature", Weather.MAX_TEMPERATURE/2, weather.getTemperature(), 0.0 ); }</programlisting> <para> Now I'll change the <classname>Weather</classname>'s <methodname>randomize</methodname> to half the temperature when it is raining: </para> <programlisting>public void randomize() { temperature = MIN_TEMPERATURE + rng.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE); isRaining = rng.nextDouble() < CHANCE_OF_RAIN; if( isRaining ) temperature *= 0.5; }</programlisting> <para>That was easy! I'll just run my tests and... whoops! The <methodname>testRandomTemperatureRaining</methodname> test failed. Not only that, my <methodname>testRandomTemperatureSunny</methodname> test failed as well! Why did that happen? The behaviour I added to the <methodname>randomize</methodname> method should not have had an affect when it was not raining. </para> </section> <section> <title>Test Smell: Order Shouldn't Matter</title> <para>Actually, looking at it again, I realise that I swapped the order of that statements that randomized the rain and temperature. The tests now initialise the stream of mock random numbers in the wrong order. This is not good: tests should not be tied to the internal implementation details of the class. They should specify only its externally visible behaviour. How can I make my tests less brittle? </para> <para> There should not be any externally visible dependency between randomising the temperature and randomising the rain. A way to remove the dependency is to pass <emphasis>two</emphasis> random number generators to the <classname>Weather</classname> class, one for the temperature and one for the rain. My tests can then mock each generator independently to force a particular outcome, no matter what order the <classname>Weather</classname> samples the generators. Here's the last test rewritten with two generators: </para> <programlisting>public void testRandomTemperatureRaining() { <emphasis>MockRandom rain_rng = new MockRandom(); rain_rng.setNextDouble(0.0); MockRandom temp_rng = new MockRandom(); Weather weather = new Weather( rain_rng, temp_rng ); temp_rng.setNextDouble( 0.0 );</emphasis> weather.randomize(); assertEquals( "should be min rainy temperature", Weather.MIN_TEMPERATURE/2, weather.getTemperature(), 0.0 ); <emphasis>temp_rng.setNextDouble( 0.5 );</emphasis> weather.randomize(); assertEquals( "should be average rainy temperature", (Weather.MIN_TEMPERATURE + Weather.MAX_TEMPERATURE)/4, weather.getTemperature(), 0.0 ); <emphasis>temp_rng.setNextDouble( 1.0 );</emphasis> weather.randomize(); assertEquals( "should be max rainy temperature", Weather.MAX_TEMPERATURE/2, weather.getTemperature(), 0.0 ); }</programlisting> <para> I can rewrite the <methodname>testRandomTemperatureSunny</methodname> test in a similar way and I also have to change the <methodname>testRandomRain</methodname> test to instantiate the <classname>Weather</classname> object with two random number generators; we'll skip over the code to save trees. </para> <para> Now I have failing tests for the behaviour I want to implement, so I need to change my Weather class to use two random number generators: </para> <programlisting>public class Weather { public static double CHANCE_OF_RAIN = 0.2; public static double MIN_TEMPERATURE = 20; // degrees C public static double MAX_TEMPERATURE = 30; // degrees C <emphasis>private Random tempRandom, rainRandom;</emphasis> private boolean isRaining = false; private double temperature = MIN_TEMPERATURE; <emphasis>public Weather( Random rainRandom, Random tempRandom ) { this.tempRandom = tempRandom; this.rainRandom = rainRandom; }</emphasis> [...] public void randomize() { temperature = MIN_TEMPERATURE + <emphasis>tempRandom</emphasis>.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE); isRaining = <emphasis>rainRandom</emphasis>.nextDouble() < CHANCE_OF_RAIN; if( isRaining ) temperature *= 0.5; } }</programlisting> <tip> Only test the order in which an object calls methods of other objects if that is an important aspect of your object's publically visible behaviour. If it is unimportant, your tests will be brittle if your they expect one particular order. </tip> <para> Finally, my <classname>MockRandom</classname> class now contains behaviour that I don't use, and that I've realised is a bad idea. I'll discard that code by restoring the original version of the class from my source code repository. </para> </section> <section> <title>Refactoring: Too Many Arguments</title> <para> My customer is happy. However, she tells me, in Formula One, teams use tyres with different treads depending on how wet the track is, so our weather class needs to simulate both if it is raining, and how wet the track is. Another important element of Formula One strategy is changing tyres when the weather changes, so the rain should start and stop at random intervals, and the track should become wetter when it is raining and dry off when it is sunny. </para> <para> All this is straightforward to implement, but my nose is twitching: my code, although functional, is smelly. I can foresee that as I add functionality to the <classname>Weather</classname> class, the implementation will become increasingly awkward because there will be too many random number generators. In particular each time I add more random behaviour I will have to change all the tests because the signature of the constructor will have changed, and the constructor will end up with far too many parameters: </para> <programlisting>public Weather( Random rainRandom, Random tempRandom, Random wetnessRandom, Random rainDurationRandom, Random dryDurationRandom ) { [...] }</programlisting> <para> I recognise this "code smell". Every time I see a method that takes a lot of arguments, I know that there is a new concept waiting to be extracted. Just as the method <methodname>drawRectangle( int x, int y, int width, int height )</methodname> indicates that a Rectangle class should be factored out and the method replaced by <methodname>draw( Rectangle r )</methodname>, so all those <classname>Random</classname> arguments indicate that there is an weather-specific source of randomness waiting to be extracted. It's time to don my refactoring hat and clear up this mess now; leaving it any later will just make more work. The first thing I need to do is define an interface for a weather-specific source of randomness: </para> <programlisting>public interface WeatherRandom { boolean nextIsRaining(); double nextTemperature(); }</programlisting> <para> I don't have to implement this interface right now. I can test the <classname>Weather</classname> class by mocking the interface, and then test and write an implementation once the <classname>Weather</classname> tests are passing. Our new <classname>WeatherRandom</classname> class simplifies our tests greatly. We no longer have to test that the <classname>Weather</classname> class compares probabilities against random numbers correctly; that will be the responsibility of the real implementation of <classname>WeatherRandom</classname>. Instead we just have to test that the <classname>Weather</classname> class stores the random weather and calculates a cooler temperature when it is raining. Here's our <methodname>testRandomTemperatureRaining</methodname> test: </para> <programlisting>public void testRandomTemperatureRaining() { final double TEMPERATURE = 20; MockWeatherRandom rng = new MockWeatherRandom() { public boolean nextIsRaining() { return true; } public double nextTemperature() { return TEMPERATURE; } }; Weather weather = new Weather( rng ); weather.randomize(); assertEquals( "temperature", TEMPERATURE/2.0, weather.getTemperature(), 0.0 ); }</programlisting> <para> Now I have to modify the <classname>Weather</classname> class to use a <classname>WeatherRandom</classname> to pass the tests: </para> <programlisting>public class Weather { <emphasis>private WeatherRandom random;</emphasis> private boolean isRaining = false; private double temperature = 0.0; <emphasis>public Weather( WeatherRandom random ) { this.random= random; }</emphasis> [...] public void randomize() { <emphasis>temperature = random.nextTemperature(); isRaining = random.nextIsRaining();</emphasis> if( isRaining ) temperature *= 0.5; } }</programlisting> <para> And finally I need to test and implement a real <classname>WeatherRandom</classname> that generates random weather using a random number generator. Each of the <classname>WeatherRandom</classname> methods can be tested individually using a MockRandom object, just as we did in our earlier tests of the <classname>Weather</classname> class. Because each method makes one call to the random number generator, no internal implementation details leak out into our tests. </para> <programlisting>public void testNextIsRaining() { MockRandom rng = new MockRandom(); WeatherRandom weather_random = new DefaultWeatherRandom(rng); rng.setNextDouble( 0.0 ); assertTrue( "is raining", weather_random.nextIsRaining() ); rng.setNextDouble( DefaultWeatherRandom.CHANCE_OF_RAIN ); assertTrue( "is not raining", !weather_random.nextIsRaining() ); rng.setNextDouble( 1.0 ); assertTrue( "is not raining", !weather_random.nextIsRaining() ); }</programlisting> <para> I can now go on to implement the additional random behaviour requested by my customer. I will define each additional random effect as a method in the <classname>WeatherRandom</classname> interface. I will also define a sensible default result in the <classname>MockWeatherRandom</classname> so that tests that do not care about the effect do not have to be changed. </para> <programlisting>public interface WeatherRandom { boolean nextIsRaining(); double nextTemperature(); <emphasis>double nextWetness(); double nextWetDuration(); double nextDryDuration();</emphasis> }</programlisting> </section> <section> <title>What Have We Learned?</title> <remark>Expand this</remark> <para> Test random behaviour by pulling the random number generator out of the object being tested and mocking it. </para> <para> Mocking random behaviour can expose implementation details by assuming how individual elements of a random number sequence will be used by the class under test. This breaks encapsulation and makes tests brittle. Avoid brittle tests by "parallelizing" your random number streams. Initialise your objects with multiple sources of randomness that can be mocked independently to test specific behaviours. </para> <para> Define application-specific random generators, rather than passing multiple random number generators into your class, so that changes to the random behaviour of your class do not cause changes to ripple through your class and all of its tests. </para> </section> </chapter> |
From: Steve F. <sm...@us...> - 2002-08-05 01:30:51
|
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/tests In directory usw-pr-cvs1:/tmp/cvs-serv9243/src/nostone/tests Modified Files: SearcherTest.java Log Message: More stuff on directories Index: SearcherTest.java =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/src/nostone/tests/SearcherTest.java,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- SearcherTest.java 4 Aug 2002 11:13:28 -0000 1.4 +++ SearcherTest.java 5 Aug 2002 01:30:48 -0000 1.5 @@ -17,7 +17,7 @@ public void testNoMatchesFound() { Searcher searcher = new Searcher(new Directory() { - public String lookFor(String searchString) { + public String searchFor(String searchString) { return null; } }); @@ -38,7 +38,7 @@ final ExpectationValue searchString = new ExpectationValue("search string"); Searcher searcher = new Searcher(new Directory() { - public String lookFor(String aSearchString) { + public String searchFor(String aSearchString) { searchString.setActual(aSearchString); return "One Result"; } |
From: Steve F. <sm...@us...> - 2002-08-05 01:30:51
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv9243/doc/xdocs Modified Files: testing_guis_1.xml Log Message: More stuff on directories Index: testing_guis_1.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/testing_guis_1.xml,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- testing_guis_1.xml 4 Aug 2002 11:13:28 -0000 1.4 +++ testing_guis_1.xml 5 Aug 2002 01:30:48 -0000 1.5 @@ -461,9 +461,9 @@ &greenbar; <para>Once again, let's see the application in action:</para> - <screenshot><mediaobject> - <imageobject><imagedata fileref="images/gui_example3.gif" format="gif" /></imageobject> - </mediaobject></screenshot> + <screenshot><mediaobject> + <imageobject><imagedata fileref="images/gui_example3.gif" format="gif" /></imageobject> + </mediaobject></screenshot> </sect1> <!-- Introducing a Directory --> @@ -485,15 +485,19 @@ </para> <programlistingco> - <areaspec> - <area id="testOneMatchFound.1" coords="12" /> + <areaspec units="linecolumn"> + <area id="expectation.1" coords="3 92" /> + <area id="actual" coords="7 92" /> + <area id="searchtext" coords="12 92" /> + <area id="expectation.2" coords="14 92" /> + <area id="verify" coords="23 92" /> </areaspec> <programlisting> public void testOneMatchFound() { final ExpectationValue searchString = new ExpectationValue("search string"); Searcher searcher = new Searcher(new Directory() { - public String lookFor(String aSearchString) { + public String searchFor(String aSearchString) { searchString.setActual(aSearchString); return "One Result"; } @@ -510,22 +514,37 @@ assertEquals("Should be status", "", ((JLabel)findNamedComponent(searcher, "status")).getText().trim()); + searchString.verify(); }</programlisting> - <para>I'll step through the changes we've made.</para> + <calloutlist> - <callout arearefs="testOneMatchFound.1"> + <callout arearefs="expectation.1 expectation.2"> + <para> + We want to check that the contents of the search field is passed through to <function>searchFor()</function> + when the button is clicked, so we tell the directory object to <emphasis>expect</emphasis> to receive + the search string we've set up. + </para> + </callout> + <callout arearefs="actual"> + <para> + When our dummy <classname>Directory</classname> object is called, we record the value that is passed + through and fail if it doesn't match our expectation. + </para> + </callout> + <callout arearefs="searchtext"> <para> We need a <classname>JTextField</classname> for people to type in their search criterion; that's not an - interesting failure, so I'll just fix that one. + interesting failure, so I'll just fix that and preload a string. + </para> + </callout> + <callout arearefs="verify"> + <para> + At the end of the test, we make sure that the search string has actually been set. This catches failures + when <function>searchFor()</function> has not been called. </para> </callout> </calloutlist> - <!-- - First, we need a <classname>JTextField</classname> for people to type in their search criterion; that's not an - interesting failure, so I'll just fix that one. Next, we want to ensure that the contents of that text field is - passed through to the directory object, so we preload the text field and define an expectation - --> </programlistingco> </sect1> <!-- Expecting the search string --> |
From: Steve F. <sm...@us...> - 2002-08-05 01:30:51
|
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/gui In directory usw-pr-cvs1:/tmp/cvs-serv9243/src/nostone/gui Modified Files: Searcher.java Directory.java Log Message: More stuff on directories Index: Searcher.java =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/src/nostone/gui/Searcher.java,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- Searcher.java 4 Aug 2002 11:13:28 -0000 1.4 +++ Searcher.java 5 Aug 2002 01:30:48 -0000 1.5 @@ -25,7 +25,7 @@ searchButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - String result = directory.lookFor(""); + String result = directory.searchFor(""); if (null == result) { statusBar.setText("No entries found"); } else { @@ -46,7 +46,7 @@ static public void main(String[] args) { Searcher searcher = new Searcher(new Directory() { - public String lookFor(String searchString) { + public String searchFor(String searchString) { return "One Result"; } }); Index: Directory.java =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/src/nostone/gui/Directory.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- Directory.java 4 Aug 2002 01:41:47 -0000 1.2 +++ Directory.java 5 Aug 2002 01:30:48 -0000 1.3 @@ -1,5 +1,5 @@ package nostone.gui; public interface Directory { - String lookFor(String searchString); + String searchFor(String searchString); } |
From: Steve F. <sm...@us...> - 2002-08-05 01:30:29
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv9181/doc/xdocs Modified Files: doc-book.xml Log Message: Added our doctorates Index: doc-book.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/doc-book.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- doc-book.xml 2 Aug 2002 21:56:03 -0000 1.1 +++ doc-book.xml 5 Aug 2002 01:30:26 -0000 1.2 @@ -14,10 +14,12 @@ <bookinfo> <author> + <honorific>Dr</honorific> <firstname>Steve</firstname> <surname>Freeman</surname> <email>st...@m3...</email> </author> <author> + <honorific>Dr</honorific> <firstname>Nat</firstname> <surname>Pryce</surname> <email>nat...@b1...</email> </author> |
From: Steve F. <sm...@us...> - 2002-08-05 01:29:52
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv8987/doc/xdocs Modified Files: extra_entities.xml Log Message: added graphics for red/green bars Index: extra_entities.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/extra_entities.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- extra_entities.xml 2 Aug 2002 21:56:03 -0000 1.1 +++ extra_entities.xml 5 Aug 2002 01:29:49 -0000 1.2 @@ -1,2 +1,2 @@ -<!ENTITY redbar "<emphasis>Red Bar</emphasis>" > -<!ENTITY greenbar "<emphasis>Green Bar</emphasis>" > \ No newline at end of file +<!ENTITY redbar SYSTEM "file:///@docpath@/red_bar.xml"> +<!ENTITY greenbar SYSTEM "file:///@docpath@/green_bar.xml"> |
From: Steve F. <sm...@us...> - 2002-08-05 01:29:24
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv8920/doc/xdocs Modified Files: htmlbook.xsl htmlbook.css Log Message: added guitbutton style Index: htmlbook.xsl =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.xsl,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- htmlbook.xsl 4 Aug 2002 11:12:31 -0000 1.3 +++ htmlbook.xsl 5 Aug 2002 01:29:21 -0000 1.4 @@ -7,5 +7,22 @@ <xsl:param name="use.extensions" select="'1'"/> <xsl:param name="html.stylesheet" select="'htmlbook.css'"/> <xsl:param name="callouts.extension" select="'1'"/> - <xsl:param name="callout.defaultcolumn" select="'78'"/> + <xsl:param name="callout.defaultcolumn" select="'80'"/> + + <xsl:template name="inline.gui"> + <xsl:param name="content"> + <xsl:call-template name="anchor"/> + <xsl:call-template name="simple.xlink"> + <xsl:with-param name="content"> + <xsl:apply-templates/> + </xsl:with-param> + </xsl:call-template> + </xsl:param> + <span class="guibutton"><xsl:copy-of select="$content"/></span> + </xsl:template> + + <xsl:template match="guibutton"> + <xsl:call-template name="inline.gui"/> + </xsl:template> + </xsl:stylesheet> Index: htmlbook.css =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.css,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- htmlbook.css 3 Aug 2002 22:23:10 -0000 1.3 +++ htmlbook.css 5 Aug 2002 01:29:21 -0000 1.4 @@ -9,5 +9,6 @@ .screen { margin-left: 5%; } .sidebar { border: double black 1px; font-size: 80%; padding: 4px; text-align: center; margin-left: 80%; } -.screenshot { text-align: center; } +.screenshot { margin-left: 20%; } .caption { font-size: 80%; font-style: italic; } +.guibutton { font-weight: bold; } \ No newline at end of file |
From: Steve F. <sm...@us...> - 2002-08-05 01:28:32
|
Update of /cvsroot/mockobjects/no-stone-unturned In directory usw-pr-cvs1:/tmp/cvs-serv8684 Modified Files: build.xml Log Message: Added support for building arbitrary fragments. Index: build.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/build.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- build.xml 4 Aug 2002 11:13:08 -0000 1.2 +++ build.xml 5 Aug 2002 01:28:29 -0000 1.3 @@ -69,46 +69,30 @@ dest="${xdoc.dtd.dir}" /> </target> - <target name="_prepare-xdoc" - depends="CallMeFirst"> - <!-- requires prepare.xdoc.dir, book.xml.file --> - <mkdir dir="${prepare.xdoc.dir}"/> - - <!-- Copy files with filtering --> - <filter token="docpath" value="${basedir}/${prepare.xdoc.dir}"/> - <filter token="xslpath" value="${basedir}/${xsl.dir}"/> - <copy file="${xdoc.dir}/${book.xml.file}" - filtering="on" - overwrite="true" - tofile="${prepare.xdoc.dir}/book.xml"/> - <copy file="${xdoc.dir}/htmlbook.xsl" - filtering="on" - overwrite="true" - todir="${prepare.xdoc.dir}"/> + <target name="PrepareXFiles" + depends="CallMeFirst, unpack-xsl"> + <mkdir dir="${out.xdoc.dir}"/> - <!-- Copy all remaining files from ${xdoc.dir} to ${out.xdoc.dir} --> - <copy todir="${prepare.xdoc.dir}"> - <fileset dir="${xdoc.dir}"> - <exclude name="*-book.xml"/> - <include name="*.xml"/> - </fileset> - </copy> + <!-- Copy all remaining files from ${xdoc.dir} to ${out.xdoc.dir} --> + <copy todir="${out.xdoc.dir}"> + <fileset dir="${xdoc.dir}"> + <exclude name="*-book.xml"/> + <exclude name="extra_entities.xml"/> + <exclude name="_template.xml"/> + <include name="*.xml"/> + </fileset> + </copy> - <antcall target="_UnpackDtd"> - <param name="xdoc.dtd.dir" value="${prepare.xdoc.dir}/dtd" /> - </antcall> + <antcall target="_UnpackDtd"> + <param name="xdoc.dtd.dir" value="${out.xdoc.dir}/dtd" /> + </antcall> </target> - <target name="prepare-book" - depends="CallMeFirst, unpack-xsl"> + <target name="CopyOtherFiles" + depends="CallMeFirst"> <mkdir dir="${out.doc.dir}"/> <mkdir dir="${out.doc.dir}/images"/> - <antcall target="_prepare-xdoc"> - <param name="prepare.xdoc.dir" value="${out.xdoc.dir}"/> - <param name="book.xml.file" value="doc-book.xml"/> - </antcall> - <!-- Copy the images --> <copy todir="${out.doc.dir}/images"> <fileset dir="${xdoc.dir}/images"/> @@ -127,8 +111,37 @@ </copy> </target> + <target name="_CopyFilesWithFiltering"> + <!-- requires source.xml.file, target.xml.file --> + + <filter token="docpath" value="${basedir}/${out.xdoc.dir}"/> + <filter token="xslpath" value="${basedir}/${xsl.dir}"/> + <filter token="fragment.name" value="${fragment.name}"/> + <filter token="element" value="${element}"/> + <copy file="${xdoc.dir}/${source.xml.file}" + filtering="on" + overwrite="true" + tofile="${out.xdoc.dir}/${target.xml.file}"/> + <copy file="${xdoc.dir}/extra_entities.xml" + filtering="on" + overwrite="true" + todir="${out.xdoc.dir}"/> + <copy file="${xdoc.dir}/htmlbook.xsl" + filtering="on" + overwrite="true" + todir="${out.xdoc.dir}"/> + </target> + + <target name="PrepareBook" + depends="CallMeFirst, PrepareXFiles, CopyOtherFiles"> + <antcall target="_CopyFilesWithFiltering"> + <param name="source.xml.file" value="doc-book.xml"/> + <param name="target.xml.file" value="book.xml"/> + </antcall> + </target> + <target name="html-book" - depends="prepare-book" + depends="PrepareBook" description="Generate book as a single HTML file."> <delete file="${out.doc.dir}/book.html" /> @@ -139,6 +152,32 @@ style="${out.xdoc.dir}/htmlbook.xsl" processor="trax" /> </target> + + <target name="CheckFragmentProperties"> + <fail unless="fragment.name" message="Requires -Dfragment.name=<fragment name>" /> + <fail unless="element" message="Requires -Delement=<element type>" /> + </target> + + <target name="PrepareFragment" + depends="CallMeFirst, PrepareXFiles, CopyOtherFiles"> + <antcall target="_CopyFilesWithFiltering"> + <param name="source.xml.file" value="_template.xml"/> + <param name="target.xml.file" value="_template.xml"/> + </antcall> + </target> + + <target name="html-fragment" + depends="CheckFragmentProperties, PrepareFragment" + description="Generate part of a book as a single HTML file."> + + <delete file="${out.doc.dir}/${fragment.name}.html" /> + + <xslt basedir="${out.xdoc.dir}" + in="${out.xdoc.dir}/_template.xml" + out="${out.doc.dir}/${fragment.name}.html" + style="${out.xdoc.dir}/htmlbook.xsl" + processor="trax" /> + </target> <target name="clean" depends="CallMeFirst" |
From: Steve F. <sm...@us...> - 2002-08-05 01:28:32
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv8684/doc/xdocs Added Files: _template.xml Log Message: Added support for building arbitrary fragments. --- NEW FILE: _template.xml --- <?xml version="1.0"?> <!DOCTYPE @element@ [ <!ENTITY % docbook SYSTEM "file:///@docpath@/dtd/docbookx.dtd"> <!ENTITY % extra_entities SYSTEM "file:///@docpath@/extra_entities.xml"> <!ENTITY @fragment.name@ SYSTEM "file:///@docpath@/@fragment.name@.xml"> %docbook; %extra_entities; ]> <article> &@fragment.name@; </article> |
From: Steve F. <sm...@us...> - 2002-08-05 01:27:48
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv8490/doc/xdocs Added Files: green_bar.xml red_bar.xml Log Message: Added wrappers for red/green bar wrappers. Shrunk images --- NEW FILE: green_bar.xml --- <screenshot><mediaobject> <imageobject><imagedata fileref="images/green_bar.gif" format="gif" /></imageobject> <textobject><phrase>A Green Bar</phrase></textobject> </mediaobject></screenshot> --- NEW FILE: red_bar.xml --- <screenshot><mediaobject> <imageobject><imagedata fileref="images/red_bar.gif" format="gif" /></imageobject> <textobject><phrase>A Red Bar</phrase></textobject> </mediaobject></screenshot> |
From: Steve F. <sm...@us...> - 2002-08-05 01:27:48
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/images In directory usw-pr-cvs1:/tmp/cvs-serv8490/doc/xdocs/images Modified Files: green_bar.gif red_bar.gif Log Message: Added wrappers for red/green bar wrappers. Shrunk images Index: green_bar.gif =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/images/green_bar.gif,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 Binary files /tmp/cvshsy9lh and /tmp/cvs2qg1zo differ Index: red_bar.gif =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/images/red_bar.gif,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 Binary files /tmp/cvsLqemYi and /tmp/cvsm9I8Lr differ |
From: Steve F. <sm...@us...> - 2002-08-05 01:27:15
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv8393/doc/xdocs Added Files: quickmock.xml Log Message: Added Nat's paper --- NEW FILE: quickmock.xml --- <?xml version='1.0'?> <!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [ <!ENTITY QM "Quick Mock"> ]> <article> <title>A Quick Run Through Through &QM;</title> <chapterinfo> <authorblurb> <author><personname> <honorific>Dr.</honorific> <firstname>Nat</firstname> <surname>Pryce</surname> </personname></author> <email>nat...@b1...</email> </authorblurb> <copyright> <year>2002</year> <holder><forename>Nat</forename> <surname>Pryce</surname></holder> </copyright> </chapterinfo> <section><title>Introduction</title> <para> QuickMock is a <ulink url="http://java.sun.com">Java</ulink> library that helps the developer build <ulink url="http://www.mockobjects.com">mock objects</ulink> for use in unit tests. It uses JUnit and the Java Reflection API, particularly the dynamic <classname>java.lang.reflect.Proxy</classname> class introduced in JDK 1.3. </para> <para> There are several existing tools and libraries for building mock objects. Why another? What does &QM; bring the the party that other tools do not? Here's a short list of features that drove the design of &QM;: </para> <variablelist> <varlistentry> <term>In-line Definition</term> <listitem> <para> Mocked behaviour and expectations can be defined inline in your JUnit tests, right where they are used. You don't have to write additional classes, or complicate your build process by generating mock classes from your interfaces. </para> </listitem> </varlistentry> <varlistentry> <term>Simple API</term> <listitem> <para> The &QM; API consists of one class. There is no need to learn a large class library or write a library of Mock classes for each API that you want to test against. Java Reflection does all that work for you.</para> </listitem> </varlistentry> <varlistentry> <term>Well Known Syntax</term> <listitem> <para> Expectations are defined using Java expressions and JUnit assertions, rather than by composing expectation objects into what is, in effect, the abstract syntax tree of an interpreted little-language.</para> </listitem> </varlistentry> <varlistentry> <term>Works Well with IDEs and Refactoring Tools</term> <listitem> <para> Expectations are defined by implementing methods of the interface to be mocked. A base expectation class with no behaviour can be generated in any IDE by automatically stubbing out the methods of the interface. Automatic refactorings applied to the mocked interface, such as renaming methods, interfaces or classes, will propagate through to the expectation objects in your unit tests.</para> </listitem> </varlistentry> <varlistentry> <term>Smooth Path To Reusable Abstractions</term> <listitem> <para> Although you can define mocks inline in your JUnit tests, &QM; does not force you to do so. You can refactor mocked behaviour onto reusable abstractions in a number of increasingly elaborate ways, starting by defining higher level assertions in your test classes, to defining basic preconditions in your expectation class, all the way to deriving custom mock classes from the <classname>Mock</classname> class.</para> </listitem> </varlistentry> <varlistentry> <term>Support for Java Idioms</term> <listitem> <para> &QM; supports Java Bean properties and Command/Query separation. This support simplifies the mocking of many interfaces that would otherwise require hand coding.</para> </listitem> </varlistentry> </variablelist> <para> Taking off the rose tinted spectacles for a moment, there must be some drawbacks to using &QM;. Drawbacks I have experienced include: <itemizedlist> <listitem> <para>You can only mock the behaviour of interfaces. If you want to mock the behaviour of a class (<classname>java.util.Random</classname>, for example) you will have to write it by hand or use another mock objects library.</para> </listitem> <listitem> <para>Methods defined by the Object class, such as <methodname>toString</methodname>, <methodname>equals</methodname> or <methodname>hashCode</methodname>, cannot be mocked up by the Reflection API. If you want to mock the behaviour of these methods you have to write a mock class by deriving from <classname>Mock</classname>. </para> </listitem> </itemizedlist> </para> <para> Hopefully you now have an idea of what &QM; is for, and why you might want to use it. Read on to see what &QM; looks like in action... </para> </section> <section><title>Creating Mock Objects</title> <para> The examples in this chapter assume you know how to write Java Servlets. If you don't, browse the tutorial and documentation on <ulink url="http://java.sun.com">Sun's Java site</ulink>. The Servlet API is very simple, but writing a Servlet exercises every aspect of the &QM; library. </para> <para> In this example we are writing a servlet that implements a simple phone book. The user will pass a name to the servlet as a parameter in the URL, and the servlet will return a page containing the phone number corresponding to that name, or an error message. </para> <para> The first thing we need to do is write a test for the <methodname>doGet</methodname> method of the servlet, and to do that we need to mock up those objects in the servlent's environment that are passed as arguments to <methodname>doGet</methodname>, the request and the response. Using &QM;, this is very easy: just create two instances of the <classname>Mock</classname> class. We will give them names so that they are properly identified in error messages. </para> <programlisting>import com.b13media.quickmock.Mock; class PhonebookServletTest extends junit.framework.TestCase { public PhonebookServletTest( String test ) { super(test); } public void testDoAdd() throws ServletException, IOException { PhonebookServlet servlet = new PhonebookServlet(); <emphasis>Mock request = new Mock("Request"); Mock response = new Mock("Response");</emphasis> [...] } } </programlisting> <para> Ok, we've created the mock request and response, but they are instances of <classname>Mock</classname> and we have to pass them to the servlet as instances of <classname>HTTPServletRequest</classname> and <classname>HTTPServletResponse</classname>. To do this, we ask the mocks to dynamically create those interfaces by calling <methodname>createInterface</methodname>; the mocks will then provide the implementation of the interfaces that they create. There are several overloaded versions of the <methodname>createInterface</methodname> method, but the simplest usually suffices. It takes a reference to the <classname>Class</classname> of the interface we want to mock, and returns an <classname>Object</classname> reference of an instance of the interface that delegates method calls to the mock that created it. The returned reference must be cast to the interface type that was created because of Java's static type system but the cast will always succeed. </para> <programlisting>public void testDoAdd() throws ServletException, IOException { PhonebookServlet servlet = new PhonebookServlet(); Mock request = new Mock("Request"); Mock response = new Mock("Response"); [...] <emphasis>servlet.doGet( (HTTPServletRequest)request.createInterface(HTTPServletRequest.class), (HTTPServletResponse)response.createInterface(HTTPServletResponse.class) );</emphasis> [...] } </programlisting> <tip> You can call <methodname>createInterface</methodname> on a <classname>Mock</classname> instance multiple times or pass it an array of multiple interface classes to create. The mock object will act as the implementation of all those interfaces. </tip> </section> <section><title>Defining Expectations</title> <para> Now we need to set up our test environment to test expectations and implement stub behaviour. We do this by telling the mock objects what method calls to expect. Our servlet will query the name parameter from the request object, and then set the content type of the response before getting an a <classname>PrintWriter</classname> to write the response body. </para> <para> Let's start by mocking the request. Our servlet will query the name to look up by calling the request's <methodname>getParameter</methodname> method. We have to set up an expectation of that call by calling the <methodname>expect</methodname> method on the mock request and passing it a "call expectation" object. A call expectation is an object of a class that implements a single method of an interface. That method identifies the method that is expected to be called, checks the parameters against expected values, and returns a canned result. </para> <para> The easiest way to define an expectation is as an anonymous inner class because the definition of the expectation is inline with the unit test, alongside its use. However, if an interface declares more than one method, the compiler will not let us implement just one method in an inner class so we have to declare a concrete class that has stub implementations of all the methods of the interface. &QM; will never actually call any of those stubs so we don't care how they are implemented and can therefore generate the stub class automatically in our IDE: </para> <programlisting>class HTTPServletRequestExpectation implements HTTPServletRequest { [...] } </programlisting> <tip> Make your unit tests easy to read by using a regular and descriptive naming convention for these stub classes. For example, we name them all by appending "Expectation" to the name of the stubbed out interface. </tip> <para> We can now create anonymous subclasses of <classname>HTTPServletRequestExpectation</classname> in our unit test to define the expected calls to the request object: </para> <programlisting>public void testLookupNumber() throws ServletException, IOException { PhonebookServlet servlet = new PhonebookServlet(); Mock request = new Mock("Request"); Mock response = new Mock("Response"); <emphasis>request.expect( new HTTPServletRequestExpectation() { public String getParameter( String key ) { assertEquals( "expect to get the name parameter", "name", key ); return "Elvis Presley"; } } );</emphasis> [...] servlet.doGet( (HTTPServletRequest)request.createInterface(HTTPServletRequest.class), (HTTPServletResponse)response.createInterface(HTTPServletResponse.class) ); [...] } </programlisting> <para> The expectation will assert that when the servlet calls <methodname>getParameter</methodname> it asks for the "name" parameter. The mock request will fail if the servlet calls some other method instead of <methodname>getParameter</methodname>. It does not, however, assert that the servlet actually does call the <methodname>getParameter</methodname> method. To assert that all the expected calls have been made to the mock objects our test must call their <methodname>verify</methodname> methods: </para> <programlisting>public void testLookupNumber() throws ServletException, IOException { PhonebookServlet servlet = new PhonebookServlet(); Mock request = new Mock("Request"); Mock response = new Mock("Response"); <emphasis>request.expect( new HTTPServletRequestExpectation() { public String getParameter( String key ) { assertEquals( "expect to get the name parameter", "name", key ); return "Elvis Presley"; } } );</emphasis> [...] servlet.doGet( (HTTPServletRequest)request.createInterface(HTTPServletRequest.class), (HTTPServletResponse)response.createInterface(HTTPServletResponse.class) ); [...] <emphasis>request.verify(); response.verify();</emphasis> } </programlisting> <para> Now we have to define the expected calls to the response object. Again, we use our IDE to generate a <classname>HTTPServletResponseExpectation</classname> class, anonymous subclasses of which will define expected calls to methods on the <classname>HTTPServletResponse</classname> interface. The first thing our servlet will do to the response is set the MIME type of the generated content: </para> <programlisting>public void testLookupNumber() throws ServletException, IOException { PhonebookServlet servlet = new PhonebookServlet(); Mock request = new Mock("Request"); Mock response = new Mock("Response"); request.expect( new HTTPServletRequestExpectation() { public String getParameter( String key ) { assertEquals( "expect to get the name parameter", "name", key ); return "Elvis Presley"; } } ); <emphasis>response.expect( new HTTPServletResponseExpectation() { public void setContentType( String type ) { assertEquals( "expected HTML type", "text/html", type ); } } );</emphasis> [...] servlet.doGet( (HTTPServletRequest)request.createInterface(HTTPServletRequest.class), (HTTPServletResponse)response.createInterface(HTTPServletResponse.class) ); [...] request.verify(); response.verify(); } </programlisting> <para> Now we have to mock writing the content into the response object. The response returns a <classname>PrintWriter</classname>. Do we have to mock that as well? No. We don't really care about the exact format of the generated content, only that it contains the expected phone number somewhere within it. So rather than checking the output data as it is written, which will tie our tests too closely to presentation details and be complicated to implement, we can can create a <classname>StringWriter</classname> in our test, return a <classname>PrintWriter</classname> that decorates it from our mocked implementation of <methodname>getWriter()</methodname>, and then search for the expected phone number at the end of the test. </para> <programlisting>public void testLookupNumber() throws ServletException, IOException { PhonebookServlet servlet = new PhonebookServlet(); Mock request = new Mock("Request"); Mock response = new Mock("Response"); <emphasis>final StringWriter response_body = new StringWriter(); String expected_number = [...];</emphasis> request.expect( new HTTPServletRequestExpectation() { public String getParameter( String key ) { assertEquals( "expect to get the name parameter", "name", key ); return "Elvis Presley"; } } ); response.expect( new HTTPServletResponseExpectation() { public void setContentType( String type ) { assertEquals( "expected HTML type", "text/html", type ); } } ); <emphasis>response.expect( new HTTPServletResponseExpectation() { public PrintWriter getWriter() { return new PrintWriter(response_body); } } );</emphasis> servlet.doGet( (HTTPServletRequest)request.createInterface(HTTPServletRequest.class), (HTTPServletResponse)response.createInterface(HTTPServletResponse.class) ); <emphasis>assertTrue( "response body contains expected phone number", response_body.toString().indexOf(expected_number) >= 0 );</emphasis> request.verify(); response.verify(); } </programlisting> <tip> You must declare as final any local variables that are accessed by mocked calls. Unfortunately that means that mocked methods cannot change the values of those variables. See the section about Java Bean Properties for a way to alleviate this drawback. </tip> <para> So, there we are, our first test is finished. Now we just have to actually <emphasis>write</emphasis> the <classname>PhonebookServlet</classname> class. We'll leave that as an excercise for the reader! </para> </section> <section><title>Command-Query Separation</title> <para> Our first test works, but is rather "brittle" because of how we declared the expected call to <methodname>getParameter</methodname>. The <methodname>getParameter</methodname> method doesn't change the state of the response object, so it doesn't actually matter how many times the servlet calls it. However, we have declared that it will be called exactly once. As we extend our servlet class we might change the implementation of <methodname>doGet</methodname> to get the name property more than once and that would break our test. We have exposed internal implementation details in a test of the class' public API. </para> <para> Let's review why we are using Mock objects. We want mock the implementation of other objects in the environment of the object under test so that they returned canned results and so that we can check that the object under test modifies its environment in the way we expect. So our tests should check the expectations of method calls that query the environment (queries) differently from those that modify the environment (commands). </para> <para> &QM; supports Command-Query Separation by letting a unit test declare expected queries. A query is defined just like the method expectations we have seen so far, but &QM; allows a query to be called any number of times, or even not call it at all. Let's change our test to specify that <methodname>getParameter</methodname> is a query: </para> <programlisting>public void testLookupNumber() throws ServletException, IOException { PhonebookServlet servlet = new PhonebookServlet(); Mock request = new Mock("Request"); Mock response = new Mock("Response"); final StringWriter response_body = new StringWriter(); String expected_number = [...]; <emphasis>request.query( new HTTPServletRequestExpectation() { public String getParameter( String key ) { assertEquals( "expect to get the name parameter", "name", key ); return "Elvis Presley"; } } );</emphasis> response.expect( new HTTPServletResponseExpectation() { public void setContentType( String type ) { assertEquals( "expected HTML type", "text/html", type ); } } ); response.expect( new HTTPServletResponseExpectation() { public PrintWriter getWriter() { return new PrintWriter(response_body); } } ); servlet.doGet( (HTTPServletRequest)request.createInterface(HTTPServletRequest.class), (HTTPServletResponse)response.createInterface(HTTPServletResponse.class) ); assertTrue( "response body contains expected phone number", response_body.toString().indexOf(expected_number) >= 0 ); request.verify(); response.verify(); } </programlisting> <para> The mocked implementation of a query can be changed dynamically during a test. This allows you to mock an object in which a command affects the result of a subsequent query by changing the mocked query within the mocked implementation of the command. However, doing this is a sign that your test might be becoming too large, testing too much in one go, and requires refactoring. Or, you might be trying to mock a Java Bean property; read the next section to find out how to do that. </para> </section> <section><title>Support For Java Bean Properties</title> <para> Bean properties are widely used in Java APIs so &QM; provides specific support for this idiom. Properties don't need explicit expectations. %QM; traps calls to property setters and getters and manages the storage of property values. A mock object handles calls to property setters by storing the new property value, and handles property getters by returning a previously stored value or reporting a test failure if no value is available. </para> <para> Tests can explicitly declare property expectations to check preconditions of the getter and setter methods and to define the initial value of the property. </para> <para> Can test and assert that a property is set on a mock object. </para> <para> Next expectation overrides properties. </para> </section> <section><title>Abstracting Common Expectations</title> <para> Expectations are inner classes, so can abstract common expectation code into assertions in the test class. </para> <para> Can define common preconditions in the stub methods in the expectation class. </para> </section> <section><title>Refactoring our Application Classes</title> <para> </para> <programlisting>public void testLookupNumber() throws ServletException, IOException { PhonebookServlet servlet = new PhonebookServlet(); Mock phonebook = new Mock("Phonebook"); Mock request = new Mock("Request"); Mock response = new Mock("Response"); final StringWriter response_body = new StringWriter(); <emphasis>final String requested_name = [...]; final String requested_number = [...]; phonebook.query( new PhonebookExpectation() { public String lookup( String name ) { assertEquals( requested_name, name ); return requested_number; } } ); request.query( new HTTPServletRequestExpectation() { public String getParameter( String key ) { assertEquals( "expect to get the name parameter", "name", key ); return requested_name; } } );</emphasis> response.expect( new HTTPServletResponseExpectation() { public void setContentType( String type ) { assertEquals( "expected HTML type", "text/html", type ); } } ); response.expect( new HTTPServletResponseExpectation() { public PrintWriter getWriter() { return new PrintWriter(response_body); } } ); <emphasis>servlet.setPhonebook( (Phonebook)phonebook.createInterface(Phonebook.class) );</emphasis> servlet.doGet( (HTTPServletRequest)request.createInterface(HTTPServletRequest.class), (HTTPServletResponse)response.createInterface(HTTPServletResponse.class) ); assertTrue( "response body contains expected phone number", response_body.toString().indexOf(expected_number) >= 0 ); request.verify(); response.verify(); } </programlisting> </section> <section><title>Deriving Custom Mock Classes</title> </section> <para> Can derive custom mock objects from <classname>Mock</classname> and mix hand coded mock behaviour with expectation objects. </para> <section><title>Summary</title> </section> </article> |
From: Steve F. <sm...@us...> - 2002-08-04 11:13:31
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv17626/doc/xdocs Modified Files: testing_guis_1.xml Log Message: Started second GUI test Index: testing_guis_1.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/testing_guis_1.xml,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- testing_guis_1.xml 4 Aug 2002 01:41:47 -0000 1.3 +++ testing_guis_1.xml 4 Aug 2002 11:13:28 -0000 1.4 @@ -375,7 +375,7 @@ <programlisting> public interface Directory { - String lookFor(String searchString); + String search(); }</programlisting> <para> @@ -385,7 +385,7 @@ <programlisting> public void testNoMatchesFound() { Searcher searcher = new Searcher(<emphasis>new Directory() { - public String lookFor(String searchString) { + public String search() { return null; } }</emphasis>); @@ -393,7 +393,7 @@ public void testOneMatchFound() { Searcher searcher = new Searcher(<emphasis>new Directory() { - public String lookFor(String searchString) { + public String search() { return "One Result"; } }</emphasis>); @@ -443,7 +443,7 @@ searchButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - <emphasis>String result = directory.lookFor(""); + <emphasis>String result = directory.search(); if (null == result) { statusBar.setText("No entries found"); } else { @@ -470,19 +470,75 @@ <sect1> <title>Expecting the search string</title> <para> - We're missing an important part of the behaviour, there's no way for users to specify what they're looking - for. The <classname>ActionListener</classname> simply passes an empty string to the - <classname>Directory</classname>. Looking again, the requirements say nothing about a user typing in a - search string, everybody just assumed it. Clearly this is a contrived example, but these things do happen in - the Real World. The nice thing about the early feedback from incremental development is that you discover - problems when you still have time to do something about them, rather than after all the time (and money) has - been spent. We confirm our suspicions with our customer that they actually wanted to be able to enter search - strings and go back to work on the tests. + We're missing some important behaviour, there's no way for users to specify what they're looking + for. The <function>search</function> method does not expect any input values. Looking again, the + requirements say nothing about a user typing in a search string, everybody just assumed it. We confirm our + suspicions with our customers that they actually wanted to be able to enter a search string and go back to + work on the tests. </para> + + <para> + Now we need a text field for the search string, and we need to make sure that the contents of this field is + passed to the <classname>Directory</classname> at the beginning of a search. We'll change + <function>search()</function> to <function>searchFor(String searchString)</function> to reflect its new + function. We rework the test as follows: + </para> + + <programlistingco> + <areaspec> + <area id="testOneMatchFound.1" coords="12" /> + </areaspec> + <programlisting> +public void testOneMatchFound() { + final ExpectationValue searchString = new ExpectationValue("search string"); + + Searcher searcher = new Searcher(new Directory() { + public String lookFor(String aSearchString) { + searchString.setActual(aSearchString); + return "One Result"; + } + }); + + ((JTextField)findNamedComponent(searcher, "search criterion")).setText("Search String"); + + searchString.setExpected("Search String"); + ((JButton)findNamedComponent(searcher, "search button")).doClick(); + + assertEquals("Should be result", + "One Result", + ((JTextArea)findNamedComponent(searcher, "results")).getText()); + assertEquals("Should be status", + "", + ((JLabel)findNamedComponent(searcher, "status")).getText().trim()); +}</programlisting> + <para>I'll step through the changes we've made.</para> + <calloutlist> + <callout arearefs="testOneMatchFound.1"> + <para> + We need a <classname>JTextField</classname> for people to type in their search criterion; that's not an + interesting failure, so I'll just fix that one. + </para> + </callout> + </calloutlist> + + <!-- + First, we need a <classname>JTextField</classname> for people to type in their search criterion; that's not an + interesting failure, so I'll just fix that one. Next, we want to ensure that the contents of that text field is + passed through to the directory object, so we preload the text field and define an expectation + --> + </programlistingco> + </sect1> <!-- Expecting the search string --> <sect1> <title>What have we learned?</title> + <para> + Clearly this is a contrived example, but these things do happen in + the Real World. The nice thing about the early feedback from incremental development is that you discover + problems when you still have time to do something about them, rather than after all the time (and money) has + been spent. + </para> + </sect1> <!-- What have we learned? --> </chapter> <!-- Second test --> |
From: Steve F. <sm...@us...> - 2002-08-04 11:13:31
|
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/tests In directory usw-pr-cvs1:/tmp/cvs-serv17626/src/nostone/tests Modified Files: SearcherTest.java Log Message: Started second GUI test Index: SearcherTest.java =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/src/nostone/tests/SearcherTest.java,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- SearcherTest.java 4 Aug 2002 01:41:48 -0000 1.3 +++ SearcherTest.java 4 Aug 2002 11:13:28 -0000 1.4 @@ -8,6 +8,8 @@ import javax.swing.*; import java.awt.*; +import com.mockobjects.ExpectationValue; + public class SearcherTest extends TestCase { public SearcherTest(String name) { super(name); @@ -33,12 +35,18 @@ public void testOneMatchFound() { + final ExpectationValue searchString = new ExpectationValue("search string"); + Searcher searcher = new Searcher(new Directory() { - public String lookFor(String searchString) { + public String lookFor(String aSearchString) { + searchString.setActual(aSearchString); return "One Result"; } }); + ((JTextField)findNamedComponent(searcher, "search criterion")).setText("Search String"); + + searchString.setExpected("Search String"); ((JButton)findNamedComponent(searcher, "search button")).doClick(); assertEquals("Should be result", |
From: Steve F. <sm...@us...> - 2002-08-04 11:13:30
|
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/gui In directory usw-pr-cvs1:/tmp/cvs-serv17626/src/nostone/gui Modified Files: Searcher.java Log Message: Started second GUI test Index: Searcher.java =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/src/nostone/gui/Searcher.java,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- Searcher.java 4 Aug 2002 01:41:47 -0000 1.3 +++ Searcher.java 4 Aug 2002 11:13:28 -0000 1.4 @@ -13,6 +13,10 @@ JButton searchButton = new JButton("Search"); searchButton.setName("search button"); + JTextField searchString = new JTextField(); + searchString.setName("search criterion"); + searchString.setColumns(15); + final JLabel statusBar = new JLabel(" "); statusBar.setName("status"); @@ -30,7 +34,11 @@ } }); - getContentPane().add(searchButton, BorderLayout.NORTH); + Box searchBar = Box.createHorizontalBox(); + searchBar.add(searchString); + searchBar.add(searchButton); + + getContentPane().add(searchBar, BorderLayout.NORTH); getContentPane().add(results, BorderLayout.CENTER); getContentPane().add(statusBar, BorderLayout.SOUTH); pack(); |
From: Steve F. <sm...@us...> - 2002-08-04 11:13:12
|
Update of /cvsroot/mockobjects/no-stone-unturned In directory usw-pr-cvs1:/tmp/cvs-serv17535 Modified Files: build.xml Log Message: Added extension support Index: build.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/build.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- build.xml 2 Aug 2002 21:58:26 -0000 1.1 +++ build.xml 4 Aug 2002 11:13:08 -0000 1.2 @@ -114,6 +114,10 @@ <fileset dir="${xdoc.dir}/images"/> </copy> + <copy todir="${out.doc.dir}/images/callouts"> + <fileset dir="${xsl.dir}/images/callouts"/> + </copy> + <!-- Copy other files (non XML) --> <copy todir="${out.doc.dir}"> <fileset dir="${xdoc.dir}"> |