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: Jeff M. <cus...@us...> - 2002-08-14 14:16:50
|
Update of /cvsroot/mockobjects/mockobjects-java/src/j2ee/1.3/com/mockobjects/servlet In directory usw-pr-cvs1:/tmp/cvs-serv16028/src/j2ee/1.3/com/mockobjects/servlet Modified Files: MockHttpServletRequest.java Log Message: Added support for getContentType Index: MockHttpServletRequest.java =================================================================== RCS file: /cvsroot/mockobjects/mockobjects-java/src/j2ee/1.3/com/mockobjects/servlet/MockHttpServletRequest.java,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- MockHttpServletRequest.java 30 Apr 2002 11:51:50 -0000 1.5 +++ MockHttpServletRequest.java 14 Aug 2002 14:16:46 -0000 1.6 @@ -1,9 +1,6 @@ package com.mockobjects.servlet; -import com.mockobjects.ExpectationSet; -import com.mockobjects.MapEntry; -import com.mockobjects.MockObject; -import com.mockobjects.ReturnObjectList; +import com.mockobjects.*; import javax.servlet.ServletInputStream; import javax.servlet.RequestDispatcher; @@ -23,20 +20,21 @@ private Dictionary myParameters = new Hashtable(); private Dictionary myHeaders = new Hashtable(); private HttpSession myHttpSession; - private String myContentType; + private String myContentTypeToReturn; private String myPathInfo; private String myRemoteAddress; private String myRequestURI; private String myMethod; private ServletInputStream myInputStream; private java.security.Principal myUserPrincipal; - private ExpectationSet mySetAttributes = + private final ExpectationSet mySetAttributes = new ExpectationSet(MockHttpServletRequest.class.getName() + ".setAttribute"); - private ExpectationSet myRemoveAttributes = new ExpectationSet( + private final ExpectationSet myRemoveAttributes = new ExpectationSet( MockHttpServletRequest.class.getName() + ".removeAttribute"); - private ReturnObjectList myAttributesToReturn + private final ReturnObjectList myAttributesToReturn = new ReturnObjectList("attributes"); + private final ExpectationValue myContentType = new ExpectationValue("content type"); public void setupGetAttribute(Object anAttributeToReturn) { myAttributesToReturn.addObjectToReturn(anAttributeToReturn); @@ -66,12 +64,19 @@ } public String getContentType() { - notImplemented(); - return null; + return myContentTypeToReturn; + } + + public void setupGetContentType(String aContentType) { + myContentTypeToReturn = aContentType; + } + + public void setExpectedContentType(String aContentType) { + this.myContentType.setExpected(aContentType); } public void setContentType(String contentType) { - this.myContentType = contentType; + this.myContentType.setActual(contentType); } public String getContextPath() { @@ -135,7 +140,7 @@ public String getParameter(String paramName) { String[] values = getParameterValues(paramName); - if(values == null) + if (values == null) return null; return values[0]; |
From: Nat P. <np...@us...> - 2002-08-14 13:56:27
|
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/random In directory usw-pr-cvs1:/tmp/cvs-serv8595/src/nostone/random Added Files: DefaultWeatherRandom.java MockRandom.java MockWeatherRandom.java Weather.java WeatherRandom.java WeatherRandomTest.java WeatherTest.java Log Message: Source code examples for random chapter. --- NEW FILE: DefaultWeatherRandom.java --- package nostone.random; import java.util.Random; public class DefaultWeatherRandom implements WeatherRandom { static final double CHANCE_OF_RAIN = 0.2; static final double MIN_TEMPERATURE = 20; static final double MAX_TEMPERATURE = 30; static final double TEMPERATURE_RANGE = (MAX_TEMPERATURE-MIN_TEMPERATURE); private Random _rng; public DefaultWeatherRandom( Random rng ) { _rng = rng; } public boolean nextIsRaining() { return _rng.nextDouble() < CHANCE_OF_RAIN; } public double nextTemperature() { return MIN_TEMPERATURE + _rng.nextDouble() * TEMPERATURE_RANGE; } } --- NEW FILE: MockRandom.java --- package nostone.random; import java.util.Random; public class MockRandom extends Random { private double nextDouble = 0.0; public void setNextDouble( double d ) { nextDouble = d; } public double nextDouble() { return nextDouble; } } --- NEW FILE: MockWeatherRandom.java --- package nostone.random; import com.mockobjects.ExpectationCounter; public class MockWeatherRandom implements WeatherRandom { private ExpectationCounter nextIsRainingCounter = new ExpectationCounter("nextIsRaining"); private boolean nextIsRainingResult = false; private ExpectationCounter nextTemperatureCounter = new ExpectationCounter("nextTemperature"); private double nextTemperatureResult = 0.0; public void setupNextIsRaining( boolean result ) { nextIsRainingCounter.setExpected(1); nextIsRainingResult = result; } public boolean nextIsRaining() { nextIsRainingCounter.inc(); return nextIsRainingResult; } public void setupNextTemperature( double result ) { nextTemperatureCounter.setExpected(1); nextTemperatureResult = result; } public double nextTemperature() { nextTemperatureCounter.inc(); return nextTemperatureResult; } } --- NEW FILE: Weather.java --- package nostone.random; import java.util.Random; public class Weather { private WeatherRandom random; private boolean isRaining = false; private double temperature = 0.0; public Weather( WeatherRandom random ) { this.random= random; } public boolean isRaining() { return isRaining; } public double getTemperature() { return temperature; } public void randomize() { temperature = random.nextTemperature(); isRaining = random.nextIsRaining(); if( isRaining ) temperature *= 0.5; } } --- NEW FILE: WeatherRandom.java --- package nostone.random; public interface WeatherRandom { boolean nextIsRaining(); double nextTemperature(); } --- NEW FILE: WeatherRandomTest.java --- package nostone.random; import junit.framework.TestCase; public class WeatherRandomTest extends TestCase { public WeatherRandomTest(String test) { super(test); } public void testNextIsRaining() { MockRandom random = new MockRandom(); WeatherRandom weather_random = new DefaultWeatherRandom(random); random.setNextDouble( 0.0 ); assertTrue( "is raining", weather_random.nextIsRaining() ); random.setNextDouble( DefaultWeatherRandom.CHANCE_OF_RAIN ); assertTrue( "is not raining", !weather_random.nextIsRaining() ); random.setNextDouble( 1.0 ); assertTrue( "is not raining", !weather_random.nextIsRaining() ); } public void testNextTemperature() { MockRandom random = new MockRandom(); WeatherRandom weather_random = new DefaultWeatherRandom(random); random.setNextDouble( 0.0 ); assertEquals( "should be min temperature", DefaultWeatherRandom.MIN_TEMPERATURE, weather_random.nextTemperature(), 0.0 ); random.setNextDouble( 0.5 ); assertEquals( "should be average temperature", 0.5*(DefaultWeatherRandom.MIN_TEMPERATURE + DefaultWeatherRandom.MAX_TEMPERATURE), weather_random.nextTemperature(), 0.0 ); random.setNextDouble( 1.0 ); assertEquals( "should be max temperature", DefaultWeatherRandom.MAX_TEMPERATURE, weather_random.nextTemperature(), 0.0 ); } } --- NEW FILE: WeatherTest.java --- package nostone.random; import junit.framework.TestCase; public class WeatherTest extends TestCase { public WeatherTest( String test ) { super(test); } public void testRandomRaining() { MockWeatherRandom random; Weather weather; random = new MockWeatherRandom(); random.setupNextIsRaining(true); weather = new Weather( random ); weather.randomize(); assertTrue( "is raining", weather.isRaining() ); random = new MockWeatherRandom(); random.setupNextIsRaining(false); weather = new Weather( random ); weather.randomize(); assertTrue( "is not raining", !weather.isRaining() ); } public void testRandomTemperatureSunny() { final double TEMPERATURE = 20; MockWeatherRandom random = new MockWeatherRandom(); random.setupNextIsRaining( false ); random.setupNextTemperature( TEMPERATURE ); Weather weather = new Weather( random ); weather.randomize(); assertEquals( "temperature", TEMPERATURE, weather.getTemperature(), 0.0 ); } public void testRandomTemperatureRaining() { final double TEMPERATURE = 20; MockWeatherRandom random = new MockWeatherRandom(); random.setupNextIsRaining( true ); random.setupNextTemperature( TEMPERATURE ); Weather weather = new Weather( random ); weather.randomize(); assertEquals( "temperature", TEMPERATURE/2.0, weather.getTemperature(), 0.0 ); } } |
From: Nat P. <np...@us...> - 2002-08-14 13:55:23
|
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/random In directory usw-pr-cvs1:/tmp/cvs-serv8362/src/nostone/random Log Message: Directory /cvsroot/mockobjects/no-stone-unturned/src/nostone/random added to the repository |
From: Nat P. <np...@us...> - 2002-08-14 13:55:03
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv8105/doc/xdocs Modified Files: random.xml Log Message: Incorporated changes suggested by TimM. Wrote introduction. Index: random.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/random.xml,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- random.xml 10 Aug 2002 23:43:41 -0000 1.5 +++ random.xml 14 Aug 2002 13:54:58 -0000 1.6 @@ -1,26 +1,35 @@ <chapter status="draft"> - <title>Random Acts</title> +<title>Random Acts</title> - <chapterinfo> - <author>Nat Pryce</author> - <copyright><year>2002</year> <holder>Nat Pryce</holder></copyright> - </chapterinfo> +<chapterinfo> + <author>Nat Pryce</author> + <copyright><year>2002</year> <holder>Nat Pryce</holder></copyright> +</chapterinfo> - <section> - <title>Introduction</title> - - <comment>Expand this</comment> - - <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> +<section> +<title>Introduction</title> - <comment>How do we test randomness?</comment> +<para> +Pseudo-random behaviour is used in many applications. +For example, in video games pseudo-randomness 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> - </section> +<para> +Testing random behaviour is not complex. You will not need to use +statistical analysis, unless you are actually writing a new random +number generator. Instead, as in many test driven designs, you +will end up separating the objects that direct activity from +those that effect activity so that you can mock one to test +the other. In the case of random behaviour, the object directing +the activity is a random number generator. You can use a mock +generator to feed deterministic values into the objects under test. +So far, soo good, but it turns out that testing random behaviour +highlights interesting subtleties with test driven design in general. +</para> +</section> <section> <title>Come Rain...</title> @@ -53,19 +62,19 @@ </para> <programlisting lang="java">public void testRandomRain() { - MockRandom rng = new MockRandom(); + MockRandom random = new MockRandom(); - Weather weather = new Weather( rng ); + Weather weather = new Weather( random ); - rng.setNextDouble( 0.0 ); + random.setNextDouble( 0.0 ); weather.randomize(); assertTrue( "is raining", weather.isRaining() ); - rng.setNextDouble( Weather.CHANCE_OF_RAIN ); + random.setNextDouble( Weather.CHANCE_OF_RAIN ); weather.randomize(); assertTrue( "is not raining", !weather.isRaining() ); - rng.setNextDouble( 1.0 ); + random.setNextDouble( 1.0 ); weather.randomize(); assertTrue( "is not raining", !weather.isRaining() ); }</programlisting> @@ -98,11 +107,11 @@ public static double CHANCE_OF_RAIN = 0.2; - private Random rng; + private Random random; private boolean isRaining = false; - public Weather( Random rng ) { - this.rng = rng; + public Weather( Random random ) { + this.random = random; } public boolean isRaining() { @@ -110,10 +119,9 @@ } public void randomize() { - isRaining = rng.nextDouble() < CHANCE_OF_RAIN; + isRaining = random.nextDouble() < CHANCE_OF_RAIN; } }</programlisting> - </section> @@ -171,23 +179,23 @@ <programlisting>public void testRandomTemperatureSunny() { - MockRandom rng = new MockRandom(); + MockRandom random = new MockRandom(); final double SUNNY = 1.0; - Weather weather = new Weather( rng ); + Weather weather = new Weather( random ); - rng.setNextDoubles( new double[] { SUNNY, 0.0 } ); + random.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 } ); + random.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 } ); + random.setNextDoubles( new double[] { SUNNY, 1.0 } ); weather.randomize(); assertEquals( "should be max temperature", Weather.MAX_TEMPERATURE, weather.getTemperature(), 0.0 ); @@ -203,12 +211,12 @@ <emphasis>public static double MIN_TEMPERATURE = 20; public static double MAX_TEMPERATURE = 30;</emphasis> - private Random rng; + private Random random; private boolean isRaining = false; <emphasis>private double temperature = MIN_TEMPERATURE;</emphasis> - public Weather( Random rng ) { - this.rng = rng; + public Weather( Random random ) { + this.random = random; } public boolean isRaining() { @@ -220,36 +228,36 @@ }</emphasis> public void randomize() { - isRaining = rng.nextDouble() < CHANCE_OF_RAIN; + isRaining = random.nextDouble() < CHANCE_OF_RAIN; <emphasis>temperature = MIN_TEMPERATURE + - rng.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE);</emphasis> + random.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 +the the one I just wrote, except that it expects the temperatures to be half those when sunny. </para> <programlisting>public void testRandomTemperatureRaining() { - MockRandom rng = new MockRandom(); + MockRandom random = new MockRandom(); final double RAIN = 0.0; - Weather weather = new Weather( rng ); + Weather weather = new Weather( random ); - rng.setNextDoubles( new double[] { RAIN, 0.0 } ); + random.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 } ); + random.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 } ); + random.setNextDoubles( new double[] { RAIN, 1.0 } ); weather.randomize(); assertEquals( "should be max rainy temperature", Weather.MAX_TEMPERATURE/2, weather.getTemperature(), 0.0 ); @@ -262,9 +270,9 @@ <programlisting>public void randomize() { temperature = MIN_TEMPERATURE + - rng.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE); + random.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE); - isRaining = rng.nextDouble() < CHANCE_OF_RAIN; + isRaining = random.nextDouble() < CHANCE_OF_RAIN; if( isRaining ) temperature *= 0.5; }</programlisting> @@ -275,9 +283,9 @@ <methodname>randomize</methodname> method should not have had an effect when it was not raining. </para> - </section> + <section> <title>Test Smell: Order Shouldn't Matter</title> @@ -303,30 +311,37 @@ </para> <programlisting>public void testRandomTemperatureRaining() { - <emphasis>MockRandom rain_rng = new MockRandom(); - rain_rng.setNextDouble(0.0); + <emphasis>MockRandom rain_random = new MockRandom(); + rain_random.setNextDouble(0.0); - MockRandom temp_rng = new MockRandom(); + MockRandom temp_random = new MockRandom(); - Weather weather = new Weather( rain_rng, temp_rng ); + Weather weather = new Weather( rain_random, temp_random ); - temp_rng.setNextDouble( 0.0 );</emphasis> + temp_random.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> + <emphasis>temp_random.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> + <emphasis>temp_random.setNextDouble( 1.0 );</emphasis> weather.randomize(); assertEquals( "should be max rainy temperature", Weather.MAX_TEMPERATURE/2, weather.getTemperature(), 0.0 ); }</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> I can rewrite the <methodname>testRandomTemperatureSunny</methodname> test in a similar way and I also have to change the @@ -366,15 +381,8 @@ } }</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 +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. @@ -443,21 +451,72 @@ 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 +<classname>WeatherRandom</classname>. Instead we 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: +calculates a cooler temperature when it is raining. +However, we also have to test that our <classname>Weather</classname> +class does not make multiple calls to the methods of the +<classname>WeatherRandom</classname> object. If it does, my tests +are showing me that I need to define define more methods in the +<classname>WeatherRandom</classname> interface to avoid more problems like +we have just encountered. +I will create a <classname>MockWeatherRandom</classname> object that +uses the <classname>ExpectationCounter</classname> class from the +Mock Objects framework: +</para> + +<remark>Need to add cross reference to the chapter on Expectations.</remark> + +<programlisting>import com.mockobjects.ExpectationCounter; + +public class MockWeatherRandom + implements WeatherRandom +{ + private ExpectationCounter nextIsRainingCounter = + new ExpectationCounter("nextIsRaining"); + private boolean nextIsRainingResult = false; + + private ExpectationCounter nextTemperatureCounter = + new ExpectationCounter("nextTemperature"); + private double nextTemperatureResult = 0.0; + + + public void setupNextIsRaining( boolean result ) { + nextIsRainingCounter.setExpected(1); + nextIsRainingResult = result; + } + + public boolean nextIsRaining() { + nextIsRainingCounter.inc(); + return nextIsRainingResult; + } + + public void setupNextTemperature( double result ) { + nextTemperatureCounter.setExpected(1); + nextTemperatureResult = result; + } + + public double nextTemperature() { + nextTemperatureCounter.inc(); + return nextTemperatureResult; + } +}</programlisting> + +<para> +Here's the new version of the +<methodname>testRandomTemperatureRaining</methodname> test that uses +the <classname>MockWeatherRandom</classname> class. The other tests +look similar; again I'll skip the code in the interests of forestry. </para> <programlisting>public void testRandomTemperatureRaining() { final double TEMPERATURE = 20; - MockWeatherRandom rng = new MockWeatherRandom() { - public boolean nextIsRaining() { return true; } - public double nextTemperature() { return TEMPERATURE; } - }; + MockWeatherRandom random = new MockWeatherRandom(); + random.setupNextIsRaining( true ); + random.setupNextTemperature( TEMPERATURE ); - Weather weather = new Weather( rng ); + Weather weather = new Weather( random ); weather.randomize(); assertEquals( "temperature", @@ -500,16 +559,16 @@ </para> <programlisting>public void testNextIsRaining() { - MockRandom rng = new MockRandom(); - WeatherRandom weather_random = new DefaultWeatherRandom(rng); + MockRandom random = new MockRandom(); + WeatherRandom weather_random = new DefaultWeatherRandom(random); - rng.setNextDouble( 0.0 ); + random.setNextDouble( 0.0 ); assertTrue( "is raining", weather_random.nextIsRaining() ); - rng.setNextDouble( DefaultWeatherRandom.CHANCE_OF_RAIN ); + random.setNextDouble( DefaultWeatherRandom.CHANCE_OF_RAIN ); assertTrue( "is not raining", !weather_random.nextIsRaining() ); - rng.setNextDouble( 1.0 ); + random.setNextDouble( 1.0 ); assertTrue( "is not raining", !weather_random.nextIsRaining() ); }</programlisting> |
From: Steve F. <sm...@us...> - 2002-08-12 23:10:23
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv9123/doc/xdocs Modified Files: doc-book.xml introduction.xml testing_guis_1.xml how_mocks_happened.xml preface.xml extra_entities.xml Log Message: reworking content Index: doc-book.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/doc-book.xml,v retrieving revision 1.9 retrieving revision 1.10 diff -u -r1.9 -r1.10 --- doc-book.xml 10 Aug 2002 23:43:41 -0000 1.9 +++ doc-book.xml 12 Aug 2002 23:10:20 -0000 1.10 @@ -17,6 +17,10 @@ <!ENTITY part_testing_guis SYSTEM "file://@docpath@/testing_guis_1.xml"> <!ENTITY chp_random SYSTEM "file://@docpath@/random.xml"> <!ENTITY notes SYSTEM "file://@docpath@/notes.xml"> + + <!ENTITY patterns SYSTEM "file://@docpath@/patterns.xml"> + <!ENTITY glossary SYSTEM "file://@docpath@/glossary.xml"> + %docbook; %extra_entities; ]> @@ -29,10 +33,10 @@ <firstname>Steve</firstname> <surname>Freeman</surname> <email>st...@m3...</email> </author> - <author> - <firstname>Nat</firstname> <surname>Pryce</surname> - <email>nat...@b1...</email> - </author> + <author> + <firstname>Nat</firstname> <surname>Pryce</surname> + <email>nat...@b1...</email> + </author> <copyright> <year>@year@</year> <holder>Steve Freeman</holder> @@ -62,15 +66,15 @@ <title>Living with Unit Tests</title> <chapter status="todo"> <title>Test organisation</title> - &todo; + &TODO; </chapter> <chapter status="todo"> <title>Test smells</title> - &todo; + &TODO; </chapter> <chapter status="todo"> <title>Retrofitting unit tests</title> - &todo; + &TODO; </chapter> </part> @@ -78,17 +82,17 @@ <chapter> <title>C++</title> - &todo; + &TODO; <comment><para>with Workshare?</para></comment> </chapter> <chapter> <title>Dynamic and metaprogramming</title> - &todo; + &TODO; </chapter> <chapter> <title>bash</title> - &todo; + &TODO; </chapter> </part> @@ -99,12 +103,25 @@ <part status="todo"> <title>Closing</title> - &todo; + &TODO; + </part> + + <part status="draft"> + <title>Appendices</title> + + <appendix status="draft"> + <title>Patterns</title> + <para>Some of the code patterns that we've referred to in the book.</para> + &patterns; + </appendix> + + <appendix status="todo"> + <title>The Expectation Library</title> + &TODO; + </appendix> + + ¬es; </part> - <appendix status="todo"> - <title>The Expectation Library</title> - &todo; - </appendix> - ¬es; + &glossary; </book> Index: introduction.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/introduction.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- introduction.xml 10 Aug 2002 23:43:41 -0000 1.2 +++ introduction.xml 12 Aug 2002 23:10:20 -0000 1.3 @@ -5,24 +5,90 @@ <title>Preface</title> <para> - In this part, we introduce Test-Driven Development. We only give a brief refresher on the basics, because there + In this part, we introduce &TDD;. We only give a brief refresher on the basics, because there are other books that are more suitable as an introduction to the technique. We do, however, spend some time introducing Mock Objects because that's how a group of us have been developing for several years and we like - what it does to our code. Let's get down to business. + what it does to our code. </para> </preface> <chapter status="todo"> <title>Test-Driven Development</title> + <mediaobject> + <imageobject><imagedata fileref="images/escher-hands.jpeg" format="jpeg" /></imageobject> + </mediaobject> + <section> - <title>Introduction and simple example</title> - &todo; + <title>Introduction: it's not about testing</title> + + <para> + <emphasis>&TDD; is not a testing technique, it's a design technique</emphasis>. + <footnote> + <para> + Or, as Keith Braithwaite put it so eloquently on the <glossterm linkend="wiki">Wiki</glossterm>, + <ulink url="http://c2.com/cgi/wiki?UnitInUnitTestIsntTheUnitYouAreThinkingOf"> + <quote>Unit in Unit Test isn't the Unit you are thinking of</quote> + </ulink> + </para> + </footnote> + Every book about &TDD; + should have that embossed in large friendly letters on its cover. &TDD; forces you, the programmer, to + think carefully about the next piece of functionality you're about to add. Before you write the code you + have to write a test that will describe exactly how you will know when you've succeeded. One way of looking + at &TDD; is as a <quote>Poor Man's Formal Methods</quote>, because it provides many of the intellectual + benefits of writing formal specifications without the overhead of learning a specification language + (such as <glossterm linkend="larch">Larch</glossterm> or <glossterm linkend="z">Z</glossterm>) but has + the powerful advantage that the results are executable. + &TDD; also forces you to pay attention to how easy your code will be to use, because the tests are the + first clients of the code—before it even exists. If you begin to find new tests hard to write, then + that tells you that it's time to spend some time working on the structure of the code, which means + <glossterm linkend="refactoring">Refactoring</glossterm>. + </para> + + <para> + &TDD; actually consists of two complementary practices, Test-first programming and Refactoring, that have a + symbiotic relationship. Test-first programming is about being focussed on what you need and precise about + what that means, but on its own it would result in scrappy, repetitive code. Refactoring is about taking + working code and improving its internal structure. Often we don't really understand what a solution should + look like until we've finished coding, no matter how much clever modeling we do. Most development shops, + however, don't refactor because (apart from holding the dubious view that code that seems to be working + is finished) it's often hard to predict what a change to the code base will do without careful investigation; + this makes refactoring risky and expensive. If we had a suite of tests to catch any errors that might arise + from making a change, then we could afford to be ambitious about improving the code. If we program test-first, + then we would have well-specified code with such a suite of tests. The two techniques really need each other. + </para> + + <para> + Once again, &TDD; is a design technique that has the valuable side-effect that it also produces a comprehensive + test suite. Design takes place when writing tests: specifying what the code should do, how its clients will + call it, and how the pieces fit together. Design also takes place when refactoring: removing duplication, + extracting and naming concepts, moving behaviour to the right places. &TDD; makes explicit that design takes + place throughout software development and that low-level code design is as important as high-level architecture. + </para> + + <para> + Model-Driven Development is about deferred gratification: I won't write code until I think I have a good + model of the structure of the problem (or the deadline is too close). &TDD; is a different kind of + deferral: I won't spend too much time thinking about the large-scale structure of the problem until I think + I've learned enough from writing and refactoring the code. When the time comes, the code will tell me how + it should be structured and I'll trust myself to see it. Some developers are uncomfortable with this + trade-off because it violates everything we've been taught about Good Practice since our first algorithms + course, and it clashes with the organisational structures most of us have always worked in. I've come to + prefer it because it's a closer match to my experience of how software development really works—and, + anyway, I never was much good at deferring gratification. + </para> </section> - <section> - <title>A quick tour of JUnit</title> - &todo; + + <section status="TODO"> + <title>A simple example</title> + &TODO; </section> + </chapter> + + <chapter status="TODO"> + <title>A quick tour of JUnit</title> + &TODO; </chapter> &how_mocks_happened; Index: testing_guis_1.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/testing_guis_1.xml,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- testing_guis_1.xml 10 Aug 2002 23:43:41 -0000 1.6 +++ testing_guis_1.xml 12 Aug 2002 23:10:20 -0000 1.7 @@ -564,13 +564,13 @@ <chapter status="todo"> <title>More test cases</title> - &todo; + &TODO; </chapter> <appendix id="findNamedComponent" status="todo"> <title>Finding GUI components</title> - &todo; + &TODO; </appendix> </part> Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- how_mocks_happened.xml 11 Aug 2002 14:09:28 -0000 1.5 +++ how_mocks_happened.xml 12 Aug 2002 23:10:20 -0000 1.6 @@ -34,7 +34,7 @@ </para> <programlisting> -public class TestRobot { +public class RobotTest { public void testGotoSamePlace() { final Position POSITION = new Position(1, 1); @@ -251,29 +251,129 @@ <para> which gives a much clearer view of where the error became visible. If finding the problem turns out to be - harder, we can trap the <classname>junit.framework.AssertionFailedError</classname> in the development + harder, we can trap the &junit; <classname>AssertionFailedError</classname> in the development tool to bring up the debugger. Then we can explore the program state at the time of the failure, without - having to step through from the beginning of the test. + having to step through from the beginning of the test. Of course, this doesn't work in every case, but most of + the time it takes you to straight the heart of the problem. </para> + </section> <!-- Breaking apart the Robot --> + <section> + <title>More complex tests</title> + <para> + Now let's revisit the second test, moving to an adjacent space. We want to ensure that exactly one move + request has been made and that it contains the right values. Testing that the request is made at most + once is straightforward: + </para> + + <programlisting> +public void testMoveOnePoint() { + final Position DESTINATION = new Position(1, 0); + + Motor mockMotor = new Motor() { + private int moveCount = 0; + + public void move(MoveRequest request) { + assertEquals("Should be move", new MoveRequest(1, MoveRequest.SOUTH), request); + moveCount++; + assertEquals("Should be first move", 1, moveCount); + } + }; + + Robot robot = new Robot(mockMotor); + robot.setCurrentPosition(new Position(0, 0)); + robot.goto(DESTINATION); - <!-- TODO --> + assertEquals("Should be destination", DESTINATION, robot.getPosition()); +}</programlisting> + + <para> + The first assertion will fail if the <classname>Robot</classname> passes an incorrect + <classname>MoveRequest</classname> or <constant>null</constant>. The second assertion will fail if the + <classname>Robot</classname> calls <function>move()</function> more than once. This test won't + fail if the <classname>Robot</classname> doesn't call <function>move()</function> at all, as it won't + have tried either assertion. We can only tell whether this failure has happened <emphasis>after</emphasis> + we've finished <function>goto()</function>, so we need to record how many times <function>move()</function> + was called. We can't just push <varname>moveCount</varname> up from the anonymous <classname>Motor</classname> + class to the test method because, unfortunately, Java requires such variables to be declared + <token>final</token> and we can't change the value of a <type>final int</type>. There are two alternatives. + </para> + <para> + First we could use a <glossterm linkend="selfshunt">Self Shunt</glossterm> instead of an anonymous class. This + means that the test class itself implements <classname>Motor</classname> so the test has direct access + to its instance fields. For example: + </para> -<para>Now I know that my Robot class works I can write a real implementation -of the Motor interface:</para> + <programlisting> +public RobotTest extends TestCase implements Motor { -<programlisting> -public class OneSpeedMotor implements Motor { + // Implementation of Motor + private int moveCount = 0; public void move(MoveRequest request) { - turnBy(request.getTurn()); - advance(request.getDistance()); + assertEquals("Should be move", new MoveRequest(1, MoveRequest.SOUTH), request); + moveCount++; + assertEquals("Should be first move", 1, moveCount); } - &elipsis; -} -</programlisting> -<para>As my tests grow, I can refactor the various mock implementations + public void testMoveOnePoint() { + final Position DESTINATION = new Position(1, 0); + + Robot robot = new Robot(this); + robot.setCurrentPosition(new Position(0, 0)); + robot.goto(DESTINATION); + + assertEquals("Should be destination", DESTINATION, robot.getPosition()); + assertEquals("Should be one move", 1, moveCount); + } +}</programlisting> + + <para> + Alternatively, we could change the outer level data type, as follows: + </para> + + <programlisting> +public void testMoveOnePoint() { + final Position DESTINATION = new Position(1, 0); + final ArrayList expectedMoveRequests = new ArrayList(); + + Motor mockMotor = new Motor() { + public void move(MoveRequest request) { + try { + assertEquals("Should be move", expectedMoveRequests.remove(0), request); + } catch (IndexOutOfBoundsException ex) { + fail("Should be more moves"); + } + } + }; + Robot robot = new Robot(mockMotor); + + expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); + robot.setCurrentPosition(new Position(0, 0)); + robot.goto(DESTINATION); + + assertEquals("Should be destination", DESTINATION, robot.getPosition()); + assertEquals("Should be no more moves", 0, expectedMoveRequests.size()); +}</programlisting> + + <para> + In this version, I'm holding the details of the expected route in <varname>expectedMoveRequests</varname>. + <!-- TODO --> <comment>Under development</comment> + </para> + + <para> + You can chose which style you prefer. I have to admit that, unreasonably, I'm biased against Self Shunt. + I can never remember which methods belong to the test class and which to the shunt, and it's a little bit + harder to separate out the two aspects later on. That said, the second approach can be a but more complicated + to write. At this level, it's a matter of taste but you should probably use only one style across your team. + </para> + + <!-- TODO --> <comment>Under development</comment> + </section> + + <section> + <title>Under development</title> + <para>As my tests grow, I can refactor the various mock implementations into a single, more sophisticated MockMotor and use it throughout all the Robot tests; for example:</para> @@ -331,9 +431,10 @@ } </programlisting> -</section> <!-- Factoring out the motor --> +</section> -<section><title>What does this mean?</title> +<section> + <title>What does this mean?</title> <para>My code moved in this direction because I was committed to unit testing but didn't want to record and expose unnecessary details about the Index: preface.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/preface.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- preface.xml 10 Aug 2002 23:43:41 -0000 1.1 +++ preface.xml 12 Aug 2002 23:10:20 -0000 1.2 @@ -75,12 +75,12 @@ <section status="todo"> <title>Organisation</title> - &todo; + &TODO; </section> <section status="todo"> <title>Acknowledgements</title> - &todo; + &TODO; </section> </preface> Index: extra_entities.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/extra_entities.xml,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- extra_entities.xml 10 Aug 2002 23:43:41 -0000 1.5 +++ extra_entities.xml 12 Aug 2002 23:10:20 -0000 1.6 @@ -2,4 +2,6 @@ <!ENTITY greenbar SYSTEM "file:@docpath@/green_bar.xml"> <!ENTITY elipsis "<emphasis>[...]</emphasis>" > -<!ENTITY todo "<comment>To Do</comment>" > +<!ENTITY TDD "Test-Driven Development"> +<!ENTITY TODO "<comment>To Do</comment>" > +<!ENTITY junit "JUnit" > |
From: Steve F. <sm...@us...> - 2002-08-12 23:09:55
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/images In directory usw-pr-cvs1:/tmp/cvs-serv8968/doc/xdocs/images Added Files: escher-hands.jpeg Log Message: added illustration --- NEW FILE: escher-hands.jpeg --- ÿØÿà $.' ",#(7),01444'9=82<.342ÿÛ I`«ÉäzAÞÑ|~F´Oé@ÄLmwÎpv? ×)lÌqâ(>¥su#W»Èç ½ôÞ@d`õ i$Ïup3¶}vÒ yPi² `c9P/ø"y°ªLPc..Mö£%»¬àÈ«*pªhò]ÚeQð&ÀP¢Û\}ÝlqM#ÆpîÇè(4Ö+m^Ì"±'&Ò«ÃÂ/ö :Ø3¦X`Ð}xë» S¦ÀòâROïg>4ÎxvéÐP*ôºCòECçùPRI 9üh>³ÔâÞEK*¨>¢ Z¶·PÜ!`Ä:c4E·~Bñµâhã']ÀØ\Ð}*]!CÝÝר HFÃlTd(IéÎ-a;]? É[³ãOá$o·ùÖiX*°|ÎÑ4Í-uKeFãÊC¾W<K ò Kþ¢«Sâ3èÎ/x"R^F@ Ò]ê±Ü@ºn;Ȥ&Ç3ð åg²Ñg) îl4òìx8Uù´x_X`bN-8Û¯Î}oc¤pXãA 6Îî÷kñÀ=V;ù¯Ë'eÃf¶à8)ÿ ¯´mj$c2gɦqÏÄùPSc¨¶§¨]·X¢=qx;3ò ]®Û~ÐHnYÇNT 1êçò »'øh4w³IkWàö²¬áÎÈÐk/ã[Í:H¤PñJ0äsÊɬc¹Òµ»)#*Ou3@ÞÂÅØ °»;¾7áÆÂK¤HÕÜüh'öxÚd ã@UXôú gu"Ç ÂÜ´cdoõ :B"BNÀ O¤~GaÚá¥ä[ óDûRÔäFÌÜYïs?Úî©e ½¡f)Âóá'#:uÙßîà ¬Ïu@À;tçAÉãMfÑø/,0F÷ ÐÏ=°B2Ü?:eÄ&;¢ '¼µØ³°AáÖ/¬K»¨M»J²ow<þb'<|#+sýh,iÊ \Èu ç÷⯺fó2¸<CÉ êLìäà ºô÷ÐèÆÍFÄ㸸ÜÛùÐidÃTU0|WcA;8±ÛïFßé@_Hd2°÷ÐÐLë8ý¬2®ØõÔP÷ãÊÆ8mßÃ.ÿ àvM_n»rHáUé¿:4//¯¾yäÐEmD` 9ÛaýèÞ\ìæ´xÁ* çJ µ¿wdsÈç@>ù{©;0¸Ph½#,aÀCÏ n?ù[ðáþo¥ú{ÇÖü3îþu/Ä ¡ÿ |
From: Steve F. <sm...@us...> - 2002-08-12 23:08:55
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv8689/doc/xdocs Modified Files: _template.xml Log Message: added glossary and patterns so that fragment will compile Index: _template.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/_template.xml,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- _template.xml 9 Aug 2002 18:46:01 -0000 1.3 +++ _template.xml 12 Aug 2002 23:08:53 -0000 1.4 @@ -1,14 +1,26 @@ <?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"> + + <!ENTITY patterns SYSTEM "file://@docpath@/patterns.xml"> + <!ENTITY glossary SYSTEM "file://@docpath@/glossary.xml"> + %docbook; %extra_entities; ]> <article> &@fragment.name@; + + <!-- need these to avoid dangling references in the fragment --> + <section> + <title>Patterns</title> + &patterns; + </section> + + &glossary; </article> |
From: Steve F. <sm...@us...> - 2002-08-12 23:08:01
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv8434/doc/xdocs Added Files: glossary.xml patterns.xml Log Message: added glossary and patterns --- NEW FILE: glossary.xml --- <glossary> <title>Glossary</title> <glossdiv> <title>L</title> <glossentry id="larch"> <glossterm>Larch</glossterm> </glossentry> </glossdiv> <!-- L--> <glossdiv> <title>R</title> <glossentry id="refactoring"> <glossterm>Refactoring</glossterm> </glossentry> </glossdiv> <!-- R --> <glossdiv> <title>W</title> <glossentry id="wiki"> <glossterm>Wiki</glossterm> </glossentry> </glossdiv> <!-- W--> <glossdiv> <title>Z</title> <glossentry id="z"> <glossterm>Z</glossterm> </glossentry> </glossdiv> <!-- Z--> </glossary> --- NEW FILE: patterns.xml --- <glossary status="draft"> <glossdiv> <title>S</title> <glossentry id="selfshunt"> <glossterm>Self Shunt</glossterm> &TODO; </glossentry> </glossdiv> <!-- S --> </glossary> |
From: Steve F. <sm...@us...> - 2002-08-12 23:07:26
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/related In directory usw-pr-cvs1:/tmp/cvs-serv8286/doc/related Added Files: TheHumbleDialogBox.pdf WorkingEffectivelyWithLegacyCode.pdf Log Message: added some related papers --- NEW FILE: TheHumbleDialogBox.pdf --- %PDF-1.2 %âãÏÓ 10 0 obj << /Length 11 0 R /Filter /FlateDecode >> stream H½WÛÜ6ýùîió"QRöeãØxÉÁ~QKìiyÔbC·ûïSE²¨ËL¼oa[¬bÕ©sß=Þ©|'SÆßßp¿ú'vóö³bá7|Ç »o3ø¾°7?Øóµo#û$SÁ$çòýÒTÇÒ´ìÞãÑôÃ-û¾mÙ¯¸l`¿²ÇÏ7g"2¡óÜÍý¡é¿Ú/«(¶È]J°¹w"g:Mw.}öö^0ÎY)Ö«á²wûgiâcïd¡ _ûx4ì§é´o {ß}bïìW·_dVjÓ+AOqÕÈBYSHÔr©ÈÌåN¨]ÌHä±Îîo+êöær·ÉdDïðàRÀóR©CÎáÿgÿÙT#ûÅt£íoÙCWíÜ ÂÌþú|! É:ü:p<O=ZÞ!ç[èäâì*{rÇ|x¼JáAKüK$®"®E½aw7*ód· ¨OÊäòºôS)ô"ÇðSþúµRù>].\ëàê?ñr`cùlXÉZkY9úzé¹^¸@ökÿ¥³SÏ}cºAQ|ä,! MJÝÆLDÖ{ûuÇØOÆ'ñq^lÿìáªåAe t¸ÀÑö#þ8Î|1=»Â,Ú\`ɸ¿übØ`Of<6Ý«ZwÈhYm}jsÁU8$×YªÐt®åùÜ6U96¶»ÅDª eZNV *å4«å ÝN%~ÂÈ >ËѵÏeØì MvjkL´D- åéa'U, x¯Õ¶XiÊtÊñÀ.eÀþM¸±zìçi#þ0; Q£ M[,ëU»U$µ¶¡Øç)ÚìHsBJ© §nfÊ~lª0B9__G'YTpYÄB¡;û °ù±¿®Óû©ï`6÷Wó¨lïLÎ^OÁ[¥dùFX°:nNV$ A7{S5¨;¸GÓ³ÚÍ@鯧Ìì ®Ó¹èeíÄìÐtîp¯Ë`R3ÜW®(ÌK¥ Ø#|Lg 70Í]8e³<%jÓmÍéL}«ïn¹}iF§ÄG Ó î-Ñ{cqBp´Íf©©yù Ú÷dFûC¨S14hÓD@¦Õkô|ö÷6N¥5_4nA§qÆ5¯dä»"rf¿ÄYlÚ*sëxÂS HuWÛn#¹ýÿC=&áoq0A À¾P-JÍnQé¦ìq¾>E²HuË,èy©:uêÔáß^:²Ý àõùAøo>ÁÃoº]~>>°ëdøìá ¿ëºÁïwøËWwùíiðð¨8ÆÄ#|·ý ÍßöGømá÷°lßÍ_áõÏÁ¼oW·m<¥C3¿CZVVå»yCÂeW< © ®)ÒgøòMW)·Íúð¯¶ç¸SÆ=éx<X4´Ó«ò_ÝtqOÜÐÛà|çbWÁoR@rÂY¯d¸HåE!³ªñT?qé77ÃdAÏ 6`Và¬^'hq¹[eÇy[Ñá,}8.zöàð>[oÏ'°f£§ð}½ù©§Ëh.ÇûSE$¿Ê.7»ÞmðWpíVðm¢ì`qãÕ[w^àå!Ä]ò$kA9Ð~Yw@+[öÌÝÅàÝúÒ½mµI\<hèÞÑ3rQϼspÍ2=Ãôû4þv2fòwr3ÅèýxV7]X4"~ýÕÃôú³ô³Ý¥mIÁª:!,w2}â!ÃuÂûà`õèNKÊ]Ãh½Ç_©híãL)N§ñNQH¾pÿev§YOH5ís9¨«tS°\m1a²â¹#®º´6ÐÊsڢغ,Bµ"©CÇõX ½Kìi· 3¶k+Iü½bâ<ÙÉíQª¢;öx43F}>]õÉ,àÞÌ;ìÃèyI ÖbÓéý.ÅF*-_¯oþ?íÍI<¬¢`¾_Û®sgúù7¸ýÞbL)fCh&¹ÈÔr"´»FF®Uº9Ú³õfüH;*¾jÓ6ËDGpØ¦Ú ¡8«6Í`ÏÁ]¤åÝ&t^ÉÜÃ#2#ð¸ö±¹ÿå/ÊYårvS«ÓFDxDç e¨_ÔÉ«DyæSéôÏc kRO©6lm *Ð&·D(¾dv'~ÖaöÝü¼QÛöá]¦j³6¾ë¨ÍnþôÁZxm;Õ2Ê$*Ââ®Cì©0&RUwÊÀHöWVj@Pïàº<ê{ÚÎëmX§üòüB]´»ÁÂ*7Ôãh÷³Ïe³áJÕæ~Q¨Ô~ ¹Ûnëè÷¿¿>üPý.GéuT¬b¥ú ¾ØÃ³{øsjAª endstream endobj 20 0 obj 2050 endobj 21 0 obj << /Type /XObject /Subtype /Image /Name /im1 /Filter /DCTDecode /Width 456 /Height 359 /BitsPerComponent 8 /ColorSpace /DeviceRGB /Length 22 0 R >> stream ÿØÿî !"(($#$% '+++,.3332-3333333333ÿÀ *:JZjzªºÊÚêúÿÝ Tê='ÌÜÛ TVZà×8Ë4h$É hü¹Sÿ µy÷öðÿ ÿ×öë«ÓPëÝ;§ÜÚ.õì´´<³ÛÖ@.µÅ `10c?¨uîÓîmzöZZYmÎkI ÚâÐH0LL1C?ôüM¾µcQmÎkI Úí bb`ÄÁ[õ³ ×u5\.u,m´Ue\[éZÆ4»sHÃG4ðA!·ë_A®êj¸\êXÛhªË)¸·Óµ,ivæiàCgÖ]ÔÖ/u¢çRÆÛEVYPu¥»±,ivæ$hàx ©»ë7Dn6&AÉýf7Ú±ÈcæÊæ° Dk hÛôô[ùÿÒô_êv'ý-:_ýnÒ½ëOúÿ jÝgþó »_GùÂÞ£è_þüáÿÕöë«ÓI%Ïee·¥}gÍÌÉ£%Ôe`âUSñ±íº_U+k`XØÝ:Lçò²Ò¾³fædÓê2°±*©øØöÝ/ªËËÁµÅ°,lLLé0c'(tϬyyä:<Z«v=Ý.ªË˵Ű,lLLé0bßUUêx}EÖ;Øù7â`±÷TàæïÙw¢HÚÆkyÀwÑ-wÕsÕzQuÁ«v>Eø Øûªpswì»Ò$mkI5¼Ã`;èÃ7êÑêK=Ïv[¨È¿¾ÚÝûmôZÒKa°ôKÿÖì±~¢d×Óñ²:§ÚÒñª£ Ï@7ieÛ6 ÏD7ieÙ6 µy÷öðÿ hÛôô[ùÿÓöë«ÓI$I$_ÿÔöë«ÓI$I$_ÿÕöë«ÓI$I$_ÿÖöë«ÓI$I$_ÿ×öë«ÓI$I$_ÿÐöë«ÓI$I$_ÿÑöë«ÓI$I$_ÿÒöë«ÓI$I"Ã`,,.Ósb[=Ä'âØlöi¹±-âAñ ÛWªá^âÍçnæÄ¶{OÄ/ÿÓôÃÓ:Ìÿ Ú-a|ͯpAÅÿ µy÷öðÿ ÍhaAÊ®¶ÔKZaÜàÿÑöë«ÓI$I$_ÿÒöë«ÓI$I$_ÿÓöë«ÓI$ÿ öËþßù öÏâOÁßï%RT"I$I/ÿÐöë«ÓI$I$_ÿÑöë«ÓI$I?ùOúSÿ µy÷öðÿ ÿ óðÿ óðÿ öËþßù öÏâOÁßï%RT"I$I/ÿÑöë«ÓI$I$_ÿÒöë«ÓI$I?ùOúSÿ µy÷öðÿ ÿ óðÿ ÑÏþCþÿ óðÿ öËþßù öÏâOÁßï%Ugþó N¾,þó Gпý/ùÂ ËÆúà ÐòXËC:Îϳ·u¸o×OkÒuÚ¬Ìo¬8Y%°3¬ìû8 KwRûítö°'XíªÍÇëØy½%°3«íô[ºZ7k§µi:ùj¿ÿÒô:~·bÙNQÂ˯%Øã÷5 ¾ÖVÇy-cL<5ÐL4¸@§ëv-ôä,ºñ²]1osXY{nµ±ÂKDØÓ t $8ίxÖQVIÃʯ!ÔkÖ^Û¬cá;DØÓÚè&Hp ß®=2ÕyuÊ®¶¶_e,ÇÚ=Ð챡¤À.-Jß®=2ÕyuÊ®¶¶^ú2Y´{ Øö¸µ+~¶ôÚö]ÅÖ;%´ËßNC1ötKì{CI¸µMß[:uXVdeWn#éÉf-Ô\Y¾§¸5äZZÚÝ긵Çk'V&ï}>¬+22«³ôä³ê.,ßSÜâI--mnõZ㵫HwÖ °ì¿&»1_VC1®¦ÒÍõ9Á®CK[[½GãZ@3ºõ´¬Ã«&öSk)¿"¦´×Mk\á»yÑí%Íih@Úwuêÿ »ü'ý)ÿ ÿÐé~©t¥ß«µÙÓ³°ìéõ4eÙÛ©tPêÈ¥µ½ÀÈ`sdµÝÕ>Ô±õv»:vn>¶»3ru.Â)gfÇop ²Ù µy÷öðÿ ÿ Yÿ òô§þBõ{ý@é_òR÷}^ÿ óðÿ óðÿ öËþßù öÏâOÁßï%fäàaçPúr©mÌà&<³2p0ó¨}9T¶æ ¿Çü«ÿÓô¿õõ{þ Õø¯Iÿ ¿Çü©o«ßñ¯Å/õõþ ×øÿ ÞÏþCþÿ ÿÑôo®ÿ µy÷öðÿ ÿ mªÆ=sK\Ò W¦ôÒ½_ÙØ8>¬zfß´ 1ñUzoÕþÒ½_ÙØ8>¬zfß´ 1ñUºÕÞÓ=OÙøx^¬oû;kfø±1&>*÷¦|[÷ wÒ?¼ßï¹ögþýßò¯ÿÐö?Lø·ïØ=#ûÍþø/Tû3ÿ ?Õïõ¥ÉJ?Þµõ{ý@é_òR÷¶>ÿ óðÿ óðÿ öËþßù öÏâOÁßï%fåcßCFK±^Øp{×|p:,¼¬kò(shÉv+ÛcZïE~-Ù4XÚr_ö ÿÒöë«Ó×~²ÑwPéu6ZrÛFS¬Úܹ3Ó-{)k / /&!ÑÇçu߬´]Òêm´å·§Yä̱þkÛKK%ãp¥äÄä³zßÖ*nÎéµ¶ZrNKä̱zd=´´¸\7 ^LAî©õ§«ãåÔ}^¥ö£_ì]µÑÓNK©vÀß[ßéÖéÝÝí@ýOëGWÇË'¨ú½Kí=F¿Ø»j?£¦Rí¾·¿ÓÓº»Û ÿ µy÷öðÿ ÿ ×Õïõ¥ÉJ?ÞØú·ÿ óðÿ 6Yeµ¾»^Ç×5ÆCäàü'ý)ÿ ü'ý)ÿ óðÿ öËþßù öÏâOÁßï%RT"I$I/ÿÕöë«ÓI$I$_ÿÖöë«ÓI$I?ùOúSÿ µy÷öðÿ ÿ óðÿ óðÿ +ÿÖÑÿ µy÷öðÿ +u~=¦\øë C\wpT¶V¨ÔÌlo^KúP¢·WècÛ«_ýa 4kîÞ?ÛÆÖjf>7©ÝK$Q[«ô(·Wm|?ôí C\wv=ºíe½ õÕ¹ZÐÆí>4Yh2Òæ»èF5Hç w\¬·¡>º·3«XÓ½§Ó-Z\×}ÐÆ² ï;0·¢>º÷3ªXÓ½§`4Yh2Òæ»èF5Hç?¯}t§¢u1¯ÅuUCo}pÝ´ÓaQ;»F¹ý{ë=¨Ù~+¬ªª{ìcí¦¬«4$hÝÚ5¡×>¸SѺ ÷c:ʪ¥·>Æ8nÚjʰÃHÔÝÚ5úë]}lt» WWm÷ÔìÖ¶ÿ jji$ÿÐöë«ÓI$I$_ÿÑöë«ÓI$I$_ÿÒöë«ÓI$I$_ÿÓöë«ÓI$I$_ÿÔöë«Ó^©Ô)é}37¨^×:¬:,½á±¥Æ$04ÕVêBÓ3z ísªÄ¢ËÞqk\bHMUn§OMé¹÷:¬Jl¹á¸µ.1$kÅf_ÖúæmQé1¶_Klh6ß]^íÕ°7î;H%ºN]ýo®`Ö×õCeøÔ±øÙ&ÆmõÕîÝ[#~á ±¨¶û²=:ªi{ÞèI'ÀÝ}ûwÜÓÓ¿êÿ T±¾ÛwOÃßéý¢ÚªÝ·qc¼JÝéöÆÁ Øåa}R»ëú§WGv0gU£¼ê²^ns[o§c¬cþÚÖm1ô§8½3ëìÚ±.Äu}I¸¾¼=µØjy{,;ãwµÚ{ñ¢ºó.M¶ú,®ûZÛ\Xñ[È-2Ø1: ùÚèÑÏpävÐ ~²ýc£§OUÌ«µì8̽â¢×Îá°&Dk(uõ¯V!ï;!Å®i¥¶¸226ÌA)z¿ÿÕñ¿Û½sö_ì¿Ú9_`ÿ NVN]î¿&×ßkãu¸¹Æ '^ÜLIó_ÿ×ðÕ̯ZÜ|JI%¸øKqñ)$ãâWÿÐðÕ̯ZÜ|JI%¸øKqñ)&·¿ÿÑðÕÌJõÇĤ¥¸ø·RãâWÿÒðÕÌJõÇĤ¥¸ø·RãâWÿÓðÕÌJõÇĤ¥¸ø·RãâWÿÔðÕÌJõÇĤ¥¸ø·RÅÿÕðÕÌJõÿ »oX»wé/Æátê§ñsöím¸ú¶tyï¹Oâõ«ûw÷fúÄúB«ÙÖoÃã¿N£þ*öËU®ûzÍÿ HTàu á516R°0WPqáÕPÐTÉâÕ561RÐ54Ð3*@E]CxæÆ \ endstream endobj 24 0 obj 49 endobj 16 0 obj << /Type /Page /Parent 5 0 R /Resources << /Font << /F0 6 0 R /F4 17 0 R >> /XObject << /im1 21 0 R >> /ProcSet 2 0 R >> /Contents [ 19 0 R 23 0 R ] >> endobj 26 0 obj << /Length 27 0 R /Filter /FlateDecode >> stream HÅWÛÛFýù~Ó7Þvį;ÙÅÆÍ GjHgüûVuWS$%Ý òÝnÙ-èu²:> s®È,ͼpcÙÁ<å#*²MaöÕãØ%Oa(lIh2¶Å$û2êQÄy°¤3NmtÍnWRÊ «êÁ<ì_ØÚ@Ëe^Éó¹Xàqîòµf RÇóкjíÛ)ÊwÎã±g[v=(|ÁÐìÒÚíLÇJsðÙP\M+# ªüdÇ-QÉ£íiª¾-UpÒ+ÔKM>æiâuPN«¾a»þð°©¥¤f'* Å:dªÀpÜeBÙTÔt£^ðåù©¾¨Ø ÐÊêÈd§ÀQ4ñ>C¥µhÙº4[f>g,Ñ(y\ £7âú Ôs¨£´¢$óHù'v>4Íí0Á-354¥Yï°;K ->Y\úø&Vr Gª£ø_ÃDÔH!ªKsÅÕÔ©ï?boÓ¾x²Ø A w¦Ü@»!Ýék¶º!ìÛU8´Ù±j60¹]«ùT,Ñ1%ºj²1¡ H|ZLÓ'þ =±«úýÆ£bnÔ*Å*t`¯µäp®ÃÍIµÃjÑÊ4\. ¬®êõ5:uCÄé fÛ×}ûÂ6vkú½ÔÓ|£þ;¦ÚºgSì ¢}[ì-¥ æÏêÊUÅ|ØvEéôìí¶s ôÚªOg[Á%:R¡IT÷@zӤúLèËØ?áK0|@ëýÚ}Oñ°Ä2¦ÿ AǦÀÉáÐã:îè{4´{*¢³Fz)û©¶kÂRÃúm°á驯ü,|à#cÒ·ã Òüŧ;¿¸CMó^@.Y@ÞÜ|¼_AÀXüÇ^ûúÎL²Æ²°,«7~O½fFd1¢&ñØý_~d¬a1â\ãwÕúéÜmÁsXü»°ÇC¸hVd±ð þJûûuÇ¥à¿ÇõW²}ÙÀ`ÿKפtå_½ñîç_r?½äþïéÃÍÛ¿ÿt÷ÏüíãO7ÿúá»Õê1ô¿å{ñ<ixµvh,ªòFPÔB«®B O-éòõÀþÿ,d4BðÌá±´Ü_o´%²õËîÈ?*ÉâOOòÜé_ÿWä/ÝX£îµèCYÓLÑp£Ýº7=r×:éᰱ̡Þ[:$¬odBiÅÀZÿ}.1)CÙ4÷¶¸+µ_"-\L¸à¶þ TêÉ÷,é$ÍÉ\KÒí /{[×?Sw°8amßãR Â<Ðé|k äD-ÚÛ®e/U<p¼¬Ê-üåó«éBOC1TØCé7"ló¢ÎÖÁmXúÊ)|xUÉXV¡Tù@Pý iXãñK¶í!Ì6¢R©S>§Ë5KèSØ`$¤R nÀ|9Íæ¡"hMé¸â»ê4\'nýH 2Ó2faoÎM6Uçh^ð> èµ!и\° HµWmoÛ8þþütH{±Ë7QR¶Wà¶]/ö°Á÷>8(6«Q$Ccd÷ßwÈ!%QrÛ»´493Î<óÌèûÕ,¥D$²ú4£ÄüÕ÷dönIIºÍönF4f¹!sX+ÃúD®>Vç:¿ß·dÍ#F8¥üÜæ}¦²ÔY»×usMþ^ä³kÈgý¬¾Î8á¸P¸:I¬iF]?é-uRþnX@X FÌqgOvþaþùJÜc>ÿHf?¬f,±§æ¤ÛHÙ(ÈH6ödv÷öeE{Eßè,½`ú{pkA)ÖQx³¾(¡àA2I@2R6&±¤&M1$%y·yVä¿éõÕúÍ(B(DÊ?ƹW»0 ½â$|ÎÒdã¢éÝÿÉôqÐÿÝv§Y*bMÆÆI,"ýã>ËËÕã¡ÌO%)b\·ËünW̺Ç$íRÚ'y¥ð×%2êLDr¨þB&@2|W ¡ÁÁMÿÉÄòBdÏëOqô+åsvb^½®wÙFø`>grÁMÉÜé~²5ü3×§ó0dê¥Íþ ¼t¯sÇ$í2òÔæõ&¡°·G#ÕÉFg<¼û<irAì#%§±Ñ¦)@v]m6ºÐ6¯Êó¦%7ä ~Uõûe^@¶ÈÛ/ÄÝ?c²Ñ=4Céoeñy/ÚdðÐyÂlÂ_'; u4 »ÛjópÍN!÷Æ_&èþVÁi*Jý¢lt$ú#9:RýQ<:J»#Æú°âïÏäø,êÏâñYÒq::ã6 ݱËàXãàØþòÇj|4&km^x 6P¤µA{Á â°Ô§^q1î*K $íSÏ#>Ø(ú åRç5Æ¿;¬¦ p¥eh^c Écd_æñ æ$8ºe¨íÌ8*¡<Τs¡¬èÑRº; °%=ËDZ²z|!¸]B¬öËÄ*¦ÆyÚé¥1²[¤1w³-'²)²¦!§î;í&dj!Úk¹[y¢Ü0ý¤ëgrlôîX,êSv:<u¥ìV ñSëÌÛôôÖ¥û&f kýÙTeϲJQÒ½ÇqÌÝsTÅÓ}UÏäp¬5yÊëö>¶9V¿R*? KÄݱ´tÞ æ½¼SàN!+·(ÊY/'ÃÈåîÕiï=í; MïÝê]^bú"Ä ?ºãÊ<¾¦À?dÐ5çíÏa.WEVÞ³{¼S 0süÄ»¿ã¬|ÙZè_@ÏZtå¾8ý¿î²ÍÃúªì|ÆÂ¤tzff¿[Záõï0¯¼ÅÒðÄ b²ÎùO½ «µwß»¤è+.ñáý÷^zÉªÅÆ- HWënÇ~½Ãü¤Si3×½´Ä±ã¤NZjó@°"âZ$WÙ]P¾{Ï9³Y^d ÂB!á©RàuÄ»b96©eéÎ<?` ¢pèÿÄêMÝ×åªþw5L_È èÐ¥©q~ó/Æü+aBÿ|»,ëÍÛfýЧò®µwebPcû#·Ö^±`k`=:-Sá´¦#Ï·?tFÒýí 3âÆ´H 3=ï@ WÊ;¥È½OvU]ªY_7u×O'zÕC`f°Ü´¯ßÛ oÞL_áZSÏO0èPx&a:XPǤ¬¹+Fñ'b ¿¤ÄkvÎiNJ;jÖ#®®¤µi&÷tYDøõëzÆZ³êÍk§+¡©xÁ®¨6ühB® {/ÕÕî¸u¨É¬$ÂÕè$a¦ís'í§V$Óß;X8!èx<¥% ölL Ò! 0,¨cRÎB5â*æÀ´Ê¿ô< AtÓïÀ¼Þ²ø>_Jâ5;í'eøÒ± äܽ'_Pq8Ô´ mf÷Ç#UfPT¶¿r¯ýöL´&ÁVmÁV!ÂB[:Ú2áV¶§Õ-äûÝÔ1²ßM¹wÕxW»éx7§Tì~£m!Âm!ÇÛ:ÚÖûĬÀ×GÛ>Õ*í:áxp6î^Ò,®ïi¾?#ÂììZcgè@Æ¿'©Ó øám#G= äaÖ`ó9Qsl¤®Ü'ªWRÛ[Í)à (ÊÅ`ù(Ç$ ÙÕ·5>ï<ɨ ÛÅkÜH¥°&íÊ·ï £i"iróîúf:qvóÌe¿]Df>`&U;}åÖòØ¥iJ^Q¯ð¦Þ#hÿqðÏËHC {@ü¨D;<¸#ÞJc´æJO,kÒFêL¹Cê³îI)=Ë_âO` ʨ´ºä¼!öÀìkòÔ*?}åÔ/Q_ñFú Lþådß'N'^(CÏ8¤ ÿç1H Ó p>ã¨Æá+_gºïÞþudß?þýo?]ÿþîÿüþ£³¦G;Nb ¹Õ¤#¶Æ:ð÷}Jñ!C5þï©AÖM:»SÕ^F³ û69î*è$|yêÍu© :*e>-cud ÝÄWÂ!Gwõ»iá«da|ÎwÙÌ}çkÞÐÞÒ´làÝÆqÄ÷ Yù³¨¸½òæT±ÐJìQä¾q:§^ï¶[좪° aQÈ#¥ð%1ïª wñ]Mãl×ú8ÆmoXÚ*vÁÿÖУ;÷"æ~ßÁÐ'ÿ¶Y{º ¶Æc¥.bÈ-kh©Ý°,ÜA˱m«n@ MNð®N½÷°lwÒVlkTÏìÍ<ð(.£Ñõ $°©(|qNÆE^ª¶Äþ¦ó΢-¡RDoç>°CËv¥,êgAâæjøÆnÝ Á^âÛ¨Ã)¨#_¤4< sëNDC#O:sYfç8¸)Å:¦Øù¼ fpuí££_ýÓ>ÚçÐ%ÝgàI³rµêl;Y=VÞ³%óqÐùTÅâdØH¾öÓíç-c6>±dJ"j/a¨]÷¼ìX_¯+ð>ÃP=sÛß«¦¹geÌ5Rä`X³Reâç,Ên·\+ñ0.SºaäPj:;¹ïíiî ݯ±Û¶Ù¡çÑøÕ[ðÉÁ÷̰+5ó¸ÜÐ4áäu@XÂÈ4C¯¸UBJÔdbL 6 HWÛn#¹ýù>z²¶dßgÉl0°»A6Fö!ºEI=n5µ}±Fòï©"«ÚMYã;ÀÒEÖåÔ9º{SI+ q÷ã)ð¿~+Þ|ÿQjUàÇ7r%«µ¸u°>«îpêín÷:SBK©¯ÅÏM½3¶w¶®Åû¶¿â¶Aüjß»Oo´,à¸QH¸º,ýÑ2:ØþɮöyßJl~ÇNR¶Ò!èå>)nT² {àÃa»îv6lÔ %y£,¢ØîµNü¶tµÜ¤s¿éʬCܪ|yþ6½ðÛB ªU¡ç¶·ãÎÅhí ¢éÖö³pÿG=õ½íÆö$ÛÚz´k±iÚÑöÂÂtÂôÛiVpçua¥¡ ) ))¹URj·?8è¨Mçççp«*¥pu§î4@£8LÐkÑ«³mÓ»=ìèïA<A访P5ú5~ài:9\uN*§ i Ì'uëðË,[ÖXI¯\e:Kfc(¦¿*ûÔØ#Tí=3îÌ(AìÜQØÒöÑ!QHð¿+ñá-q!ï&É«eÙéã$°ÍÓF¸zr %HºáêÆûÚä¿+mWïÞA-ï¯n¤"ß"lîßP&:r¥ÎF)Ëæ0(<¼~FÕsªòâ¾ôgéÓ°ûשï¯(!zY¾zóîøÇ¢ÿ¼ûß\ɯT¢ÿK5§0ÝeÔ©³ô~jñþêìÃKuàPÕW¦ÊK%ÇðõÅXÿóÿvñÆ#'qÁ³ ¨,Î ,+úaT|{$70¹v}-Z;£Ðh{Ho×à7OV¬¡e !¶HXPÃ.FBÌÔ²8X=;½à|é±oÆÑ¢±~à>Ú´IXµ§k rí·]ÂzÜyuíÞÙÿ ¡âÐeÁ5Ó®(tÈ=|zC +nT¶S2ÈT^á2iòLKRe¨Zå"ZÓc6èËܾÑó ÎTÐc>\Ø cx\@¸D$@^¯ÈeQÍî«bôWµ+)3*Óû±ò05íZL_po S~âв×d0!à@Â>\?Ì>»?ʩʴôÏÊnhÖ¶Hÿ£ M#ÌÊ5·Õº1ÛâCýÿÝÁöf<rzN¿¿ó°$åðGÁÝ ?2°èÜ÷õ8 é¥iiÆD_ %ÂÎÜvÂÁôzIþÄâLìÉg1³JÅáeÜ ýlPédã _ } X/ÑÍÓ8(BjF©ATIPô(3rãWàGCÄåâK:/¿Ô"eÒýãܶkOMn¸ù~zö2â¡åö4 Âð¬¡vO £ endstream endobj 38 0 obj 2883 endobj 36 0 obj << /Type /Page /Parent 5 0 R /Resources << /Font << /F0 6 0 R /F4 17 0 R /F5 28 0 R /F6 39 0 R >> /ProcSet 2 0 R >> /Contents 37 0 R >> endobj 43 0 obj << /Length 44 0 R /Filter /FlateDecode >> stream HÕWmoÛ8þù¼/7K2[vÝtN»è¶Cà¾(\[MtçÚ µí·²ü9Võö #QâCò!)¾\õf:Ó¬=Ä_²ÞÓSf£X¾îé#}fOømÛü¾Ío?&l½áà±D×É ÝÆ4bIi=1G~xz:®£6²¦ CÃÈM-õµUò³%¨»àl<9ñÍmÌãõ|þ.:Ws ·Ìj`Þ15Û>eRv÷ÂRGl¶®óãl¥å¾&Á7iÃRí ',ZS~ÆRþ2þàjË syqrvâ¬ïÞ-/VîѵßÜñrç}rACá÷èY¶=ívN37ÔÙ]Îú¼¯³:£¾¬SÊÑ êsGÂ7®&½0*bNHB?R)æó;¼<Nþ:e!§ ô_H·Ø¥_þël+Í`ë Íí1ضíÀK%kZ(#MµAe$ÀAÆ^ü dÈ £ÊÞa;ðR¿pä×ÇËZÔ}xÔI§ÿOÔ¼òA¿5ôõÒÈ",(¢2SUÿÙãTÿ½£Pþ>ÀåXÃê¼×K.Û±1)Ëø ZTÛR²ìûsÒ"-íìЬ§¢ªv'µddRØRx«óªß:±+æøÏ¡Aÿ 1SofrÇl'@ziç»ãf 9â4ââé!ÁÍ-ö;±]Ç ¦Ykçó ^Î|ñÀÃLËÒ´CÝ&QsÆ\¼áÒ\+k^è5õyÛÚ·j[¥T+°ÿ}¸ýÛK°²m¥¹V zªhspJåm°Å endstream endobj 44 0 obj 988 endobj 41 0 obj << /Type /Page /Parent 42 0 R /Resources << /Font << /F0 6 0 R /F5 28 0 R >> /ProcSet 2 0 R >> /Contents 43 0 R >> endobj 46 0 obj << /Length 47 0 R /Filter /FlateDecode >> stream HµWÛnGýþC?Ê^î«l`Éq¢ Kɾè¥5Ó$Ç1Ý#»Øßª¾Ì ¤lg@03ì®ë©ªS×÷, <]±ûwà_·!«÷ÉV ~^/Uq|ÌÉ%<ÇqÏ{rqÓî]¹ÙjòÀ"JX°%ùPæ[!+ò^ ½Z·UE>á1E>ÉWäþó è$ ¾þøñr³esÓÖ»ô¼+EÕnÞ¼ùØÜ6¥¶o¯¬lÆ3õNÞ¨\RêGõýÇú÷w$Þ¼dðwæ Ã/+£ið%uþÿSFå.æTzY3ï/þYÁ`oùo9~ÍÞ$KØ7ÑIÝw ¹ÿôÛßòmâYð¥üý÷ÿÍß¹ Û 8 ñP:LÎ@e MJEà`J|Ñ-©ÅSÙl¬2@Ê(1/s£Dnûú±¤0PS+Bn5QRÖTå$<´k²o»§%yì5jñh3}å>¦0£®"ûJÖå¦[¸½ßJ°×¦/m£qìn¥s¶# y/ªÖ[xYJêÆx*mUÛ>swÀso}{ª=Üí$øú/I¶âYú(ú²OÂU4I XèBñ,s^õM®Ë¶ÐëÆEØ@Bw¢ZdݵµùåÇßnI^ ¥$ù¾´Mu }£¥Ây[,*³À&-l3½gÙ@xÙOÅFb,PUÙhÎL 'ÌIÝZäÒâ)oU*èâà*ëâBVr#ÐK"<ªàÖ²ñ@0jf£qàuQîú±1äà)¯ª`ìñ³Ì!Ƕ'¹hH'Á0Ýæ º¦ pÞµJ~A¨fÀØ`¸Ë¼¯Deé° }B`ÀàB¹vb<êâ1ë ;ô~@»öØû-Ôe*RevÔSwí§r´ ¾·k¡çØ6 ò÷¦iyJû ÚZê²j¨£sq2|°DÀ{ÄÐoeÑ~ìK÷(Î$æo$Aân¤ßaÃnhʺn;vmC>øùb±ÄØ$tÓìÊõ°°4'ûâ 2Oxìß®187±uAkì'®_÷ª$ôÀo¬¦R 7Ò¢T»JLôÊ u zÂhcê1rÏ" 'MËÊ,¨ìQnʦô8¸ Á¾ HµWÛnÛFýýÃ<&©î äMÜô(bA¿¬ÉD" %eYßÙobÜI @ ;³3gçræíÝ*#ÀÓ5înVÌOoaõó{ÙZåͬIÆÍc×ø$Oðê]s8ër»ëàÅ!ì >ùNª Þ+Ùín¯àªf[Õk¸{\1"ð<¦V5qJ[¥Tá¶õ»ÂÙ4µ&á6°;® Ô; ÈÆQo>÷bmJY_KÝ·ðA$ag®Ýû ÿ uôNE±õ Â/.dV#¢åzTèN<a®!ß ó|pzÿ°ûYn0c0ZíeY· ê\Z[yC:À¾yЪÃÞ¿º0lD ×/Fë£Ëýò"iêmÈ+Ù¶¨Ü.>â"é·Ø=ândG óî(«êÜ3¤mK4.#¢d`z²4HxJ°qæ$ÏF¾hÀR/'OØHÐóýI¹s÷ Ò2Ë( T.#Ö4&#æ¸@"Φ\¤"kI¨?ç¤àñh¸BÎ4ÂÕ<"©·- µREk|0¤N;U_®¢ÄÞÑìEÓüÑ¥ DË`è}ᳫ{"éG¨±¬ß¯ºqv{tùôxB;'Ìk*æ±ØzE¯Ø_{7?£w,pµt*ïD3/iÊû0ÄpO6'BãlÈ0/ðNãfè» sØ4ÚåÙ^jØGwWRCYwJod®fJuÂèüÛ¬õ´ì Á3#áÒÑîÄdöb#EÚäf2ñ"¦¹XéeßaÜOI}Îߨ'U53Ïí*A¶¦:uHvfÛº5éüI½Mt³>ÈÉåâÄ"íáý@¹oòÏîÜ¿àÓ±;"YÚÏQO¢ÊºæËI¥z÷ªîúàEVS»R÷rÄÙÕ,Lbj2MEiè$= þ¼Ç·ªBUjklÁhëïÇë_ ´Â Ö¾ÐQ¥h¨ô±{ÄsýHOãIÛpa\eDøqÕ8ëFÞ\?gÎÞ8 ¼+b/¿MS¹öâ.sá]¯¿5 »ÉqÚ}HÌÝBô¹-TiØ«n×Dò¸W¾ Íëã®ÁÏjY®ÜÈÏ0-fúFªr£E#;uh¯ìºÂ¸:àTúÂ"2 g!RNù°Y§ B¹:,YeɼE/»5>[S½ïu¾O"4 : ]>aD¾«C'kTãêÀA7[-1X»27D"´~ìD/f·û¾úl 9â¿Kù² endstream endobj 50 0 obj 1683 endobj 48 0 obj << /Type /Page /Parent 42 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 14 0 R /F4 17 0 R >> /ProcSet 2 0 R >> /Contents 49 0 R >> endobj 6 0 obj << /Type /Font /Subtype /TrueType /Name /F0 /BaseFont /TimesNewRoman /FirstChar 32 /LastChar 255 /Widths [ 250 333 408 500 500 833 778 180 333 333 500 564 250 333 250 278 500 500 500 500 500 500 500 500 500 500 278 278 564 564 564 444 921 722 667 667 722 611 556 722 722 333 389 722 611 889 722 722 556 722 667 556 611 722 722 944 722 722 611 333 278 333 469 500 333 444 500 444 500 444 333 500 500 278 278 500 278 778 500 500 500 500 333 389 278 500 500 722 500 500 444 480 200 480 541 778 500 778 333 500 444 1000 500 500 333 1000 556 333 889 778 611 778 778 333 333 444 444 350 500 1000 333 980 389 333 722 778 444 722 250 333 500 500 500 500 200 500 333 760 276 500 564 333 760 500 400 549 300 300 333 576 453 250 333 300 310 500 750 750 750 444 722 722 722 722 722 722 889 667 611 611 611 611 333 333 333 333 722 722 722 722 722 722 722 564 722 722 722 722 722 722 556 500 444 444 444 444 444 444 667 444 444 444 444 444 278 278 278 278 500 500 500 500 500 500 500 549 500 500 500 500 500 500 500 500 ] /Encoding /WinAnsiEncoding /FontDescriptor 7 0 R >> endobj 7 0 obj << /Type /FontDescriptor /FontName /TimesNewRoman /Flags 34 /FontBBox [ -250 -216 1158 1000 ] /MissingWidth 321 /StemV 73 /StemH 73 /ItalicAngle 0 /CapHeight 891 /XHeight 446 /Ascent 891 /Descent -216 /Leading 149 /MaxWidth 965 /AvgWidth 401 >> endobj 8 0 obj << /Type /Font /Subtype /TrueType /Name /F1 /BaseFont /Arial,Bold /FirstChar 32 /LastChar 255 /Widths [ 278 333 474 556 556 889 722 238 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 333 333 584 584 584 611 975 722 722 722 722 667 611 778 722 278 556 722 611 833 722 778 667 778 722 667 611 722 667 944 667 667 611 333 278 333 584 556 333 556 611 556 611 556 333 611 611 278 278 556 278 889 611 611 611 611 389 556 333 611 556 778 556 556 500 389 280 389 584 750 556 750 278 556 500 1000 556 556 333 1000 667 333 1000 750 611 750 750 278 278 500 500 350 556 1000 333 1000 556 333 944 750 500 667 278 333 556 556 556 556 280 556 333 737 370 556 584 333 737 552 400 549 333 333 333 576 556 278 333 333 365 556 834 834 834 611 722 722 722 722 722 722 1000 722 667 667 667 667 278 278 278 278 722 722 778 778 778 778 778 584 778 722 722 722 722 667 667 611 556 556 556 556 556 556 889 556 556 556 556 556 278 278 278 278 611 611 611 611 611 611 611 549 611 611 611 611 611 556 611 556 ] /Encoding /WinAnsiEncoding /FontDescriptor 9 0 R >> endobj 9 0 obj << /Type /FontDescriptor /FontName /Arial,Bold /Flags 16416 /FontBBox [ -250 -212 1142 1000 ] /MissingWidth 317 /StemV 153 /StemH 153 /ItalicAngle 0 /CapHeight 905 /XHeight 453 /Ascent 905 /Descent -212 /Leading 150 /MaxWidth 952 /AvgWidth 479 >> endobj 12 0 obj << /Type /Font /Subtype /TrueType /Name /F2 /BaseFont /Symbol /FirstChar 30 /LastChar 255 /Widths [ 600 600 250 333 713 500 549 833 778 439 333 333 500 549 250 549 250 278 500 500 500 500 500 500 500 500 500 500 278 278 549 549 549 444 549 722 667 722 612 611 763 603 722 333 631 722 686 889 722 722 768 741 556 592 611 690 439 768 645 795 611 333 863 333 658 500 500 631 549 549 494 439 521 411 603 329 603 549 549 576 521 549 549 521 549 603 439 576 713 686 493 686 494 480 200 480 549 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 620 247 549 167 713 500 753 753 753 753 1042 987 603 987 603 400 549 411 549 549 713 494 460 549 549 549 549 1000 603 1000 658 823 686 795 987 768 768 823 768 768 713 713 713 713 713 713 713 768 713 790 790 890 823 549 250 713 603 603 1042 987 603 987 603 494 329 790 790 786 713 384 384 384 384 384 384 494 494 494 494 600 329 274 686 686 686 384 384 384 384 384 384 494 494 494 600 ] /FontDescriptor 13 0 R >> endobj 13 0 obj << /Type /FontDescriptor /FontName /Symbol /Flags 6 /FontBBox [ -250 -220 1255 1005 ] /MissingWidth 334 /StemV 109 /StemH 109 /ItalicAngle 0 /CapHeight 1005 /XHeight 503 /Ascent 1005 /Descent -220 /Leading 225 /MaxWidth 1046 /AvgWidth 600 >> endobj 14 0 obj << /Type /Font /Subtype /TrueType /Name /F3 /BaseFont /Arial /FirstChar 32 /LastChar 255 /Widths [ 278 278 355 556 556 889 667 191 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 333 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500 278 556 500 722 500 500 500 334 260 334 584 750 556 750 222 556 333 1000 556 556 333 1000 667 333 1000 750 611 750 750 222 222 333 333 350 556 1000 333 1000 500 333 944 750 500 667 278 333 556 556 556 556 260 556 333 737 370 556 584 333 737 552 400 549 333 333 333 576 537 278 333 333 365 556 834 834 834 611 667 667 667 667 667 667 1000 722 667 667 667 667 278 278 278 278 722 722 778 778 778 778 778 584 778 722 722 722 722 667 667 611 556 556 556 556 556 556 889 500 556 556 556 556 278 278 278 278 556 556 556 556 556 556 556 549 611 556 556 556 556 500 556 500 ] /Encoding /WinAnsiEncoding /FontDescriptor 15 0 R >> endobj 15 0 obj << /Type /FontDescriptor /FontName /Arial /Flags 32 /FontBBox [ -250 -212 1208 1000 ] /MissingWidth 276 /StemV 80 /StemH 80 /ItalicAngle 0 /CapHeight 905 /XHeight 453 /Ascent 905 /Descent -212 /Leading 150 /MaxWidth 1007 /AvgWidth 441 >> endobj 17 0 obj << /Type /Font /Subtype /TrueType /Name /F4 /BaseFont /Arial,BoldItalic /FirstChar 32 /LastChar 255 /Widths [ 278 333 474 556 556 889 722 238 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 333 333 584 584 584 611 975 722 722 722 722 667 611 778 722 278 556 722 611 833 722 778 667 778 722 667 611 722 667 944 667 667 611 333 278 333 584 556 333 556 611 556 611 556 333 611 611 278 278 556 278 889 611 611 611 611 389 556 333 611 556 778 556 556 500 389 280 389 584 750 556 750 278 556 500 1000 556 556 333 1000 667 333 1000 750 611 750 750 278 278 500 500 350 556 1000 333 1000 556 333 944 750 500 667 278 333 556 556 556 556 280 556 333 737 370 556 584 333 737 552 400 549 333 333 333 576 556 278 333 333 365 556 834 834 834 611 722 722 722 722 722 722 1000 722 667 667 667 667 278 278 278 278 722 722 778 778 778 778 778 584 778 722 722 722 722 667 667 611 556 556 556 556 556 556 889 556 556 556 556 556 278 278 278 278 611 611 611 611 611 611 611 549 611 611 611 611 611 556 611 556 ] /Encoding /WinAnsiEncoding /FontDescriptor 18 0 R >> endobj 18 0 obj << /Type /FontDescriptor /FontName /Arial,BoldItalic /Flags 16480 /FontBBox [ -250 -212 1174 1000 ] /MissingWidth 326 /StemV 153 /StemH 153 /ItalicAngle -11 /CapHeight 905 /XHeight 453 /Ascent 905 /Descent -212 /Leading 150 /MaxWidth 978 /AvgWidth 479 >> endobj 28 0 obj << /Type /Font /Subtype /TrueType /Name /F5 /BaseFont /CourierNew /FirstChar 32 /LastChar 255 /Widths [ 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 ] /Encoding /WinAnsiEncoding /FontDescriptor 29 0 R >> endobj 29 0 obj << /Type /FontDescriptor /FontName /CourierNew /Flags 34 /FontBBox [ -250 -300 767 1000 ] /MissingWidth 639 /StemV 109 /Ste mH 109 /ItalicAngle 0 /CapHeight 833 /XHeight 417 /Ascent 833 /Descent -300 /Leading 133 /MaxWidth 639 /AvgWidth 600 >> endobj 39 0 obj << /Type /Font /Subtype /TrueType /Name /F6 /BaseFont /TimesNewRoman,Italic /FirstChar 32 /LastChar 255 /Widths [ 250 333 420 500 500 833 778 214 333 333 500 675 250 333 250 278 500 500 500 500 500 500 500 500 500 500 333 333 675 675 675 500 920 611 611 667 722 611 611 722 722 333 444 667 556 833 667 722 611 722 611 500 556 722 611 833 611 556 556 389 278 389 422 500 333 500 500 444 500 444 278 500 500 278 278 444 278 722 500 500 500 500 389 389 278 500 444 667 444 444 389 400 275 400 541 778 500 778 333 500 556 889 500 500 333 1000 500 333 944 778 556 778 778 333 333 556 556 350 500 889 333 980 389 333 667 778 389 556 250 389 500 500 500 500 275 500 333 760 276 500 675 333 760 500 400 549 300 300 333 576 523 250 333 300 310 500 750 750 750 500 611 611 611 611 611 611 889 667 611 611 611 611 333 333 333 333 722 667 722 722 722 722 722 675 722 722 722 722 722 556 611 500 500 500 500 500 500 500 667 444 444 444 444 444 278 278 278 278 500 500 500 500 500 500 500 549 500 500 500 500 500 444 500 444 ] /Encoding /WinAnsiEncoding /FontDescriptor 40 0 R >> endobj 40 0 obj << /Type /FontDescriptor /FontName /TimesNewRoman,Italic /Flags 98 /FontBBox [ -250 -216 1165 1000 ] /MissingWidth 378 /StemV 73 /StemH 73 /ItalicAngle -11 /CapHeight 891 /XHeight 446 /Ascent 891 /Descent -216 /Leading 149 /MaxWidth 971 /AvgWidth 402 >> endobj 2 0 obj [ /PDF /Text /ImageC ] endobj 5 0 obj << /Kids [4 0 R 16 0 R 25 0 R 30 0 R 33 0 R 36 0 R ] /Count 6 /Type /Pages /Parent 51 0 R >> endobj 42 0 obj << /Kids [41 0 R 45 0 R 48 0 R ] /Count 3 /Type /Pages /Parent 51 0 R >> endobj 51 0 obj << /Kids [5 0 R 42 0 R ] /Count 9 /Type /Pages /MediaBox [ 0 0 612 792 ] >> endobj 1 0 obj << /Creator <FEFF005400680065002000480075006D0062006C00650020004400690061006C006F006700200042006F0078002E0064006F00630020002D0020004D006900630072006F0073006F0066007400200057006F00720064> /CreationDate (D:20020812080706) /Title <FEFF005400680065002000480075006D0062006C00650020004400690061006C006F006700200042006F0078002E005000440046> /Author <FEFF004D00690063006800610065006C002000460065006100740068006500720073> /Producer (Acrobat PDFWriter 4.05 for Windows NT) >> endobj 3 0 obj << /Pages 51 0 R /Type /Catalog >> endobj xref 0 52 0000000000 65535 f 0000078197 00000 n 0000077869 00000 n 0000078678 00000 n 0000002725 00000 n 0000077908 00000 n 0000068426 00000 n 0000069515 00000 n 0000069775 00000 n 0000070864 00000 n 0000000019 00000 n 0000002704 00000 n 0000071126 00000 n 0000072192 00000 n 0000072450 00000 n 0000073537 00000 n 0000051874 00000 n 0000073791 00000 n 0000074888 00000 n 0000002879 00000 n 0000005007 00000 n 0000005028 00000 n 0000051706 00000 n 0000051728 00000 n 0000051855 00000 n 0000054107 00000 n 0000052046 00000 n 0000054086 00000 n 0000075159 00000 n 0000076244 00000 n 0000056477 00000 n 0000054251 00000 n 0000056456 00000 n 0000059302 00000 n 0000056632 00000 n 0000059281 00000 n 0000062439 00000 n 0000059457 00000 n 0000062418 00000 n 0000076503 00000 n 0000077599 00000 n 0000063681 00000 n 0000078016 00000 n 0000062595 00000 n 0000063661 00000 n 0000066332 00000 n 0000063814 00000 n 0000066311 00000 n 0000068270 00000 n 0000066488 00000 n 0000068249 00000 n 0000078105 00000 n trailer << /Size 52 /Root 3 0 R /Info 1 0 R /ID [<62b3d1f73cc25f631985b95a2122ce83><62b3d1f73cc25f631985b95a2122ce83>] >> startxref 78728 %%EOF --- NEW FILE: WorkingEffectivelyWithLegacyCode.pdf --- %PDF-1.2 %âãÏÓ 10 0 obj << /Length 11 0 R /Filter /FlateDecode >> stream HW]Û8üó¼ÌÝaÆ'R,åöl`swX}¡%ÊfF ÇÿþªIÊ#y¾ FC±ÕUÕÍwWeÂÒb%2Æî~½Jýé·ìê¿%¬\éus¬Bx¬Ø- x>²¿}²So¶;Çþg"IÄ ûjªÒ-ûM+·ÓýpÃ>´-ûóïìîû_KìH,YñRÓi¤aKÚm`êA÷ºöe%¥?¾à Ï _°¸³XRÿËÊÿ%Ü/³I.ù´RÏãV¶» :zX|ût;áoåt+SÁò5è2Âè|]Xq+ÊÇ ñl~ÎC¶ôDð¿ÙþÞt[ö¹itåÌnOìq;öÞªêÄ>ÙZûíEQ®ø!UÁ©r))Þ-/^MõV®}®|v"Òn®©1DÆËXÓKFôäÓ¤.ñãIå«lªOÒ)(yðßÍw`À¾êÎÙþ}éªU¨#p{+LâÞ¿z p/ ÇË|^û}ñoëcî}ÈUe÷~»ÏwWBøræC</<è^³æêãÝU ÍåüRq/fXÈ%ÞÏ%Á4b4{%'BÅw¥GMáLE)bàøMÿPcvªÛê÷ìá7-+o¼¼£:ÒGy$stf¼=!,ùÚáþ ù {|ÙRhLã´îØI+òýXíØN lC/½qÿ«Õ`¶7(Hà}*ÓÕBÄQò|UGÄ«Y³Å.]ÍVûxðí©uòPOó5tgÄvèm¥A+üG»ÆönìÓ8IÕxP.d8~ÉTý`*Ñzë {´¦aÜëçdÇ6÷Ð ¸bVu÷ ó·a»EâÒ; q²È#¢º¡2~DËgsdGgAÝãN·û _µ9±xÛ~Àë·ÀZ¢Ìå<åIÂeügø!ü¸þ£uÝðó PX° ª##Ñ Ad7ä %¦©fÑóé|5)Óxþ=*Mü^âbÚH wîäÏrÞÒ8i\èÂ"]/*$Ëô|Se99¶ü£Â Öæñ`Ûi¿á^èB" ùx+ãQ¿ºú ¸UýV?b7K|p$cïm=VDûaÜP7ãvø×yß'ÿþì`'%ÑðÖRF;ÝcJvíxEät¦d`otÅuÔOì#ú%}vNhßaö(Ósó,¦^cº«¼fË*²¦J¡u@ç{µ8¨ÛÅ"'§¼D±ÆH88GEÍýS¾èù1ÊÒpKùÈÓ$úƦ&ÙXßGжÝ_B¹ÈÒ\¤½+ÄÉ!lïÊoÛÿdy65ðDW ÜìΧ4òóçûóÝÕÿ\¨Ó endstream endobj 11 0 obj 2194 endobj 4 0 obj << /Type /Page /Parent 5 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F2 12 0 R >> /ProcSet 2 0 R >> /Contents 10 0 R >> endobj 17 0 obj << /Length 18 0 R /Filter /FlateDecode >> stream HW]ã6üó¼LîàñJÔ¥{Û$;À»C2@ /´EÛÌÈÆÑ¿¿ê&)K3Ý=heÝìê®*þðtWF")Ö2âé§»HÐö î><F¢\oèõþ.ZGEZâq'ð,eçøþÇö<Zs8öâÅBF\Íî¨t-µêÚv+ñ±®Å/OÞÅû#Öq)°eRDÛvëÄ/ºÓöEW¼(+)=¿,¬Î +ßY®§Ö ÿ¸ããɤ³¢<ÃVH=÷[µ{¿¤£»Þl»ýʯåô&RäØWWPMcÉÅ×iêwM×EnáÓQg=¾Ö>æ ô~¯w½yÑõ(.¦?ZÔn»¶ÒÂtâ û¾3=Râ\«^Ó;Óq.Ñü s[}T/>Ý_+¸«U×!q˺pÊÂÇwOT°øsèhú]¿®KÏþ,;O(3,eî¡,¢ÜïCÁVNì9teT¯s]£\È©µ?jZÊðæéºöðDÄå FQȾÌ"¶;Ñð&åk1v=tÑøI7íp8R³m_ZCÜßO] e¬kÍ È<<C²óÕbûrl6Q/ÄýòE{j®Þ`×ÖgìµéÈrã;p×Z}º.»k©KÐw¢r)³Åfqê¿ x£±7 ²ÛB ÆE¶0 Êy9ÂÕ`;)µZ¿7k2Ìܱï^LÅsoAÊD-~|öØ fOrözBI2£¿¨zÐܪÝóÝÛýÛÚUùf+e2´\aol×3^ ðÚ£´Hg=©g=c¥]ÏnRd_¸;dt6¾Ò La4À©ÕÝP÷7î§ÎÅx%^ñîjAI=g ¤¿rãØîÈÂýì\¿¢ÙþiÐ[i§¡hoVÈ2.!wÄñú¬,éºÁ«>ð4'eGqhȲ%§î"^áØj4 ã ñ¤ÙT¦Çïh8;5Õx6ääG^Êÿcû<Yγ´Hhï_óÚKÃw§Ée~4æÖ´ôØqê'0r ÙóÕtÝáYÅjÐ ô6ó<4¦b[ Ø6®©´ç16;´ïóÓß~NZ¾ÿìÄóD,(O 1½¡ÆKÝãÀRÜ}ôíüwd*»ï¦Q nj©@ì~ ªfÀs^0ÁCc÷<·Aª¼¼dn$3 ص'p·»ÓÑßIôªïÅÍù2û74©õ¨~QòhWµd¸AÄôÉsë80®]>Áø6T%b*¦³+^Ê®Õw?ä¯ |'<¶qvÇÿCWÒIXÂ#ÐtÏHèbê{ö ü|O»Ôg<;setu7ìÁpÓ?Ï+'ÃgzÄÿj/+º5Ñ%XªúyV*àÐ_òU>èkùDïæ#7®?RìöwtõD³$ó¸þÓ±ÑϪQ§_{rѧR¬Ð1ãømâ¯âM¨ÅÇH°Îîï;Ì'FXÝN×ûÕ \GrA(i2]ÓÍäòªVwÍ=J8ØîWÌgèÆ¨i(<{iûܱ«n&s[ÚYf?ÔA¼]ÀWob@4 û¦ÃOmÅ*É·dðØÚ~hP@¯Îê,ô¹[¬îÙê¶ßjùÒ8 å)%Pèý¼¢Çøg% è|¢¨VÑì³ÉY7 ]Û%¸4¼ßsôëûôt÷?ÈÏ J endstream endobj 18 0 obj 2392 endobj 14 0 obj << /Type /Page /Parent 5 0 R /Resources << /Font << /F0 6 0 R /F2 12 0 R /F3 15 0 R >> /ProcSet 2 0 R >> /Contents 17 0 R >> endobj 20 0 obj << /Length 21 0 R /Filter /FlateDecode >> stream HWÛã¸ýþb_2ÜHÝóÌNÒÈ æ¦ÖÄÃ,úHyùbååû(/<¾äu9¾ÖdÒMfDj ®¤©´[U³ss`[y%è3R$$g/g-ÅU\¸äÂWANà)¸îµn;oµtÓµÞZÅNh°Êìð ù%ÕÃ,·mV,³(sÑqÄa'Ö\+$w® #Øb®EÙUõú=Û-mêäÉUsèaPÔo ÚÓZö#àAzB8r×ÐÐpOMKÕ}l*4|v6Àú¹°TV)Ú×éâ½V]½ë¿ÝP£ù>¦#¹³dë;øÑªÿtK9ª~èýhY2åïÉq0Ý Â»[E9îXWX©×kDþI±£« qwFwÀü÷æ¤P?¦Í»CÕë}5äÒ2±3Üï$ÏÆø¦ëÇÍ4,VÃdzê=¡X¶qÄt_ɲÍÁ¹Ë0Ï g~©åVvl y(¶MÓy Àb"ôÁî,ÕÉ yè`òAÁ«d»Q#}ìì¶×Ú¬IüX`réÆÔ.TNËæSèU ó!y:q×u9×+°o¬Ì^ïÈkxØjÕyÁºPQ) Oñ"±ká<»Å(Jg'µ²Å'³ F¹K\K*TBF¨1£x°ÿ4ÖC? uW¡ºôl18]üh+pÞQØD¼÷Ó]N¶ÖéDæîÉ+:JP;ëì:ålGMÝ5. ÀúÐR Ø~=?é׫Nz¿GÇÁçÓï×qöü>Î ¿ñêºîvÛ6åN+ÌÉîç5¦8 ²! Ckp4±1ú3÷1AvNÁ2^$É &dzèD.ÛÝl/&Ùqý£âyv&æô sáhÆÜ½%| Òõë;/0ôóõ·tB²Z¶ms².Hc&É:5¶¦«ÎÆSïo¤Î_G2Õ5 zìøèçâJmï`]LZ NÂôèõfÁP*Wh¥¸ªiT¦¿~÷¸9i>tGI6¼gt(*Èê =HcÜï+]HºÖ §iþø%âAp«æ"p?JñÈÑ?TOTî©·4ã±ëÃLS@aX;êï×ß þûÒ*£ª=(ïLoi÷Ûñ ãÔµ ÓùlNþ7ÿµ*å:U¥ßÇiÔó HuÓAÁÖJÍ^föf&óÄËHnCi%zå:sÏ4gË+çÜþ¦IÒ´~3wqøgLìMcG·¶9l¶háKö<$)üfÃÞíU¡×°]Ѭ@XZ¤fó/¾*>®§®7pÒÚ|¨Æùe{ºðȽ¢v .c ÷VOÊ¿E³Û· ϵ0ª«4³t^þVT:yüq8û +FÊÎÊ<º¦Øá!§Ú^mÂý';^Ó°i\§xÛÙ×t¤u5íFbfÕT§^íÒæ©Â¹ÆÂË©]a¿i H´WÛrÉ ýýC¿ãt_§§³ë8µ©xUûYÜ*2W¾ ¾'ªýZóé#Õ f}/O$¢ü|Px²0}RÊîé{òEÕÿI¡ºv§Õ8 òek¿imë[5Ûåo¡Qö/ÈR<9Ôh¿Âpú4Í»e-í²ö4à §xÍûÙÈÛ·Ìý÷ÍÞTÝAHY¡7ûÕ¯XÐØ8Z¼ßçÑ5¾BjMÐ1Y»z7g²pqE=êÞX3i7ã³À¡û«©^Tסi·ºLI_5VH`$4/¦S+| ðít 2V(É]öQÜâðS·=¯x^ 4ËyhgqöÔus@+Q¬¶ E½¨¶×N LÎ(Nè¿ú ³ða:5Þ%Ý«Êïå ðda'Å3Aâ Ùá§ánõ8ª GÙh®'¼+òkÒBt®b| ° àUÒ` µyÏõåa*U45µ.Ec¬ þ D;8¶ì¡f$\ªêMøLtî, HÃHöµvY9 ú x[ó~ÞÛ!Û¹Êô%4&¤JøD=éÝæ¤ð {lL+ZêK2eªar<]ýïã+Fuv;îÕW©¦®$(5Ny!¶FºMì²ZYjc²09Å6ælcî¥Æ1q#QF#¹ 8ç®ìbP,d&¥é&?æ/¤oEõ=óá¶H??~Öu_RôÞGçØBÁñ´w¶¾@ر0ÚqtXö|i¦ZD^v3ó´.ÞNiªãm¼ ámË·RÃKìPLϱԢ¥_~Ðîuù`W9QN)¥xa"ÍwsIFH]È9$uש?Gé7¡VW7»¼.Xbbz¥-µøò#ÒÅ) <`p F';_@ ÊËeÇaRkXÅ3D|ÑÇáÉaÓc?Üîǧ*Æ%²¦ÏY]aèi©õÕ.JwËzɦIV~õ¨}Oºv³ºìÒVtÉhU »ð]ªcaàtA/ä:/Tý2ͨø ³³¥Gz6b=¯qX¬4CKÝxÉÅÊQ.Tmâõ: Wnò3ªLô_{ rJ9Ìí² ìõ$§TÅå<}Q9E×å3!ÓER5b¡j?Ð,N²°Êý.iøà HWã¶ýûø_ï]E¢~'($äà°iP`h·²hHô*FÑïÞ7ü!Ë½Û W¦Uº+é¶}£^dkå5¹ç Ie}à æoæÑ¼)JíÇÆþ[r»%[l, WÁõÂ_¥×þ6N¡»Í¸.ávSýOYÊYQ&]F&Ü"jc.¹=É£*¹;øc¿îdcîÙ^«PA²^6êNvGÖJ#êeËVG¶ßGÕ?ïeßʾ9Þ³ÕÁ°ÁbO[ð7ÄÑ:4{zîgÌ4ÊçhÜ%&ÒÄ.±Ól ar½sΧý ÷b#ÁáÐ3£v©®Í-Nj6J h vbǽêûËIK¶G³°åÑèBñºýI@µéI2d]j-n[ñ"èl'ÍV·#¶ (¤9âèr$_Ô F¶W vàèJãIºö:Z%/ñ4Oñ´¸È l¶ú°Ù"jÍ'Ýmwü,÷z0ð®¿üCÉÉ[³Í6Ò,/ãÎxÐòK* ~°ïàyÒøë]~à¶ü IM«*z-§îû7¼NË4ô&·´MÓiåyTÙÒVçúvyÆÑÌëÚpú6¬AÄòw±Ûãïñ8¹sàMò/-ëÔó¬Íbo¦W_QË;¯tÎØdyhrR(k Ó["H'Ø ®y5¹qè-D§ÌÑDQQÀ pH×<M}'Q}9ÐQ'× û:I:7Bb[³$hƹl9lá(´jtNN>^Êý3)þY¶1_¼(0P³[Mzx¶Ã:1uiüo&wÜK+.KzÔ&yCîrß_ ½yè/B4Õ ( õÝIȪð} Z@ðûåÐE6ílW¾ÀÇü?Q<¯Ó@Ë4öNDÈo ?ï¥+6S;pîp ¬/'?[i飳¨A¡áÐÈñï=Üd¤3÷8çãVvä ¨Î«PÜË}.¡m×ÅävÊÑâ^q>·]×Ù,é4½øLÐx4ÉPgʸá ?â·®;þÍõ£ú5í¤¯W§§GÂÜ£Æw'^dUèñex|}GâM¢Ë>Ìó¯¬òüaÖK¤K ÿÌÝÇùEë¬ý _Þx%i2VþìÏ%FtÏ7Äîx°Ñbú²â HÍWÛÛ6ýÿüu°U¢n-ò°M íCjt_²DÛÚh%Co¢ÿÞáM·ÕÊMÍ-3gΡ~Ø®"nè0`ûãü«°úöÈ äãÃ8$ä.Øà1ë¸{_¯Uv<5°cF[Ã/YrEDÜDU¯á!Ïáã=lV4àh=qhÄBkÒ «MJk5|µ¨."U¼HÂ3Çì *lxeætW½LÔoÜâ«-|°øZSÝ7¦Ê±Ædèzóæ9ÊÔ¦è¦ wø5ìä2ÍèpëúÞd|ÄíA ÊÍ]«÷3ÀÇ<Ð.Ñv\×ð¾òYTêO1#? @C\ûûSç`{J½@íC°çpz .Ïí>ÏÞîή~ÏÄ\ð¿Ý=hÏ4dSßcÜÃÁÂ$îy0³D{¤_x§Ü¯s{̹¦®'7|k×3Ä*wN- æÕ»{íRæÌSNC©äÎûäý¥Ã5ÃÙpÇqF"ïN<ÿ{OdðH*ØçL¡¯ÈòC /¸Zu§(÷O"Á.q/bH³ B*΢HE\¡,`(¨ÚxPÎRÝ rE¦~ ±0^N¢ü%1UØÜ¤o»=<$Mçùu Øö ÎÍ(££VÂi@¹¯èª1g5b´_Vµ@#·æÏØæz$2)õLYáfë4¸H%ø8¯K½AsEÑGé®yu@nÂþ=Ëwݾ=z µÊ¶B´E10L³´jFuQ (æ\ %éFv>5§ªl'Àµ8 e'©CV¤Íç²TÄF-:ä÷ÝLõÆPËýFÆ<!g9R9Á 5Êý%Ã9º0&PÛÇ@n^Jeïs!qUm¡g÷ÛM_¾î5òuq5fD½Å*8W%úzÊVÁt%¬µ¢+¤ë)ÉqhMd<VB&5-o©×â öCß zÔmyÂþ¬Pïþ*£8J¶Fc{T¥?ߨΠÑ<´ô¶(xÍ ìJDßéK·<¿3¾g&éXÞaWEf)oª]Ã(5ó$Z¼jé÷ÿÑm¯«^¢ßK¥0ºjthõÓÝ;pû´QÎA ÄYZí9ÅN¸»³În]°¢h váxã>}ëͬ®ôJ¶©%øA¡ÆUª93¦ãå^`E©xÚêþù=PM=£/XfÛÇ£ÿ«J¥ØíB>ã@wN ¬B>\ºx~:õ7ÖúGH\Ê,½ì?¦Ú Yñ¯FýeE1}ù¶!¥üZèï;0Døò üÉù7xÛçý[*pÝ{wû¾h ÉQ´{«;ȹdTdû<þÃ9 ³ô(ùU´4»eÿ$Û=t+ZÊ5YlUÊ«ýÐatBVñÿ!CË_b¯Û¶«¿e] endstream endobj 32 0 obj 1398 endobj 30 0 obj << /Type /Page /Parent 5 0 R /Resources << /Font << /F0 6 0 R >> /ProcSet 2 0 R >> /Contents 31 0 R >> endobj 35 0 obj << /Length 36 0 R /Filter /FlateDecode >> stream HWÛnÛFýýüU.,»¼·ÈCâ4@Ú¼VäZ\"r)Aúï½Q¢¬H¶a0VäîÌ3gfæÌ õh0ÿ8ñAýµ+üöÉÌKÔãûïùiá2®) q½é]³Ù·bUJXÐ ¡ÇÖTsoQºÙ<{bP½)»iâXvAqJ¨aôx_ÃÙdùø(t? ·ÄãyźîúN6kÞêsñÕÈÏ_aàÆâô¡ùewJ¿8Bs@=SCÐtÓ/+ë÷49"0Lôf©@9à©[ý+ø¶øoqàÇDIjð8À;7"P·D{ß_x§Ýÿa¸*8çÚð"×AdI×î¼Ë²ÝâÆø&äHÏ£tT ùB>ýÏqÖLH/÷<o¤ñáÔÈóë=ùÏP<]ìÿÒìnaÇ!g5ä-vrË 4ËK54}wvBM/aÝç%ȶA}òßM°4¸¬,»á2Ðs)&iæ¥jã d¹/8nUÍÊðF£tÔP¨µo |+ysfÆ Qp~H>ÓÅFÓþNögæ²çö?» toB5K%ÌVls¾;Ýè_éúQ|¾ëo÷!¿I<ºÛõáÒ7þpøéP~$ÄEfêNu\ÚÁð¨¸ãw1u}éo©è$V-ÿÂÖÜÍ 4Ê.¶Ç ¿Þ&Ö|\C2ɳÿ9ü:!¨ endstream endobj 36 0 obj 1331 endobj 33 0 obj << /Type /Page /Parent 34 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R >> /ProcSet 2 0 R >> /Contents 35 0 R >> endobj 38 0 obj << /Length 39 0 R /Filter /FlateDecode >> stream H½WÛnÛFýýüÅ,»\ñ"®[£}¨[Bò¢\L(R R"ÿÞÙEJ4%µA#qwgÏËOËYä:t°üyæüW¿ÀìG"'×3×qC¡ÀeJÊ{¸y¨¶¯uþ XÑêºô~Ï,æ<òXd¼nîà¾(àã-,¿ÌHÀP?Þ®C"Z^èzZ¥ÔÖÀGÞðzÇSuhIxæ=AB OÑLnã©ÅDýÆ-¡ÚÂz\« ¡ûFUµ6Ú¨4]o¨#TmÎa3Ã.HN Õö÷¹0'£÷ô>1{N"0\¡oØÿ£ä°_!®«¶LAdyø'*à:N~âÔè¼*¥mòw\'Y¾ãµðWãRïÀØpUé)&¼QO3`$¼¾7< MüßÚcøIô$2§µ$-Ór!Ñ*m#ª ªH¸iÞ'ÇÐ6§n{Dð)owf¯×"ªP÷À¹Vó£»pñ cFk¼ù[Gè1N{J-ôü7¨PÔj/ÝÖùNúäÀ¬~4ü²C|¡%Wn8 «îb»?LÃóßrW²m<é¸[ÝXéSÎ÷°ÃÿV· !!=ãBjÎ _i§¼ý¶¹òÄüÓ±c¡Sãk|>hùõÌ ªRØZr¥+#Ï Y,ðÛ!ó~}#á¾,Þ¡~h*AJÚÚgh¦I×¾ÏúÊ;<FÌ[X8[êåj6y)8Õ>·?T8¡¥¯=KæªýÆÆ ^3U1ǹ5Í×kô3ö+ïå|V¼)ß hÉ)r[LprÑÌR 8.¾jJ¶q#®qøú¶ÔQQ®¬HrÜVyºmõÏLgþxgb~èÏU,ö08qxcxFë-Or¤GT2ó0Ëó¨ W²Eê9T{_²îJG~fTbNØY©;8ï4VýRu¬æmÁ7¼êU >Ìëu¼é·_³ HÕWÛnÛFýÿþÕi%fo¼¹ÈCÖEѾè &W4x±£ù÷Îì,)RW»}iYI»3gfÏýéî*æLEô»ûù3üW¯ØÕë[Îb/įWÜãa²9¬¥Ô°~b×ïªm¯Ö-[H_0ɹ±ßòtݤ]º±·EÁ>¾bw]P}ðĸ'bõ&UÄDk ûhS?Ìòcçõ'Dd±Á æ,KoØä)ûcj?ÃØnÑ£ <Т7ÐgªZ:kC§ÍósBÚMñ%Ls$Bá²Ë0§BÚ¢QÅ)þØ´rá'eÆêê1ÏLÃî«,ÿÚuÒ²¬*¿kY¶]R[øÈrÛ®órå1ö§aiÕKkHºauÏÇûj.çqSýv~èâ]×´ÕÆÔ俥EÒ4,)ÁEód uòhX[ Kø«Êí¦ê¥©éô9<ºOÍIL9°õimJôئ½±ÇáM 11:·?ï=æëH>ÜW*Ý»~Hzº =EÞ0ñp.î)ñÌ;.7²¸Æ/R¾¸^¼Z¼úÑZïÈx,\øÖéHv/¤ ÔÌaÏæDävô¤T¶:ÏÀý»µÙ²wo@Ø%ËlËm«m*à粪:C-¥Î½_Ú ¥1¸Mò¢ªYr¼N¢P[Ú㢿%ÜÓÆ´ë*kÐë=Pù ìth»ÌrÁ+ê6O»"©-°E± ¤+`ê¤ÆÞ<´qÌ çM¯Gj* ÂêHDãë'ÃP\õøÜ0õÑ\ §cþÆ>òañú$Øý{Ó1))Ïñ<i/ÎåÉ÷§I2vbZêå®=`áØ®fÙ»jI×9v3lë¿Í©É$ÂÓÆôÞÔ®k¸;Â{1AøR*ìMòɼíýÒ"ú®Â`2>£¸bF§YûTç8»¡T´§ûò\Fâ ÓÀàÞS9PÜÝvCjHä27Eæ¸xÀ°$M ôr7ëÄÓÁ;½0Z:óMßû+¼(иÕÚ~Oq@þn2:U:ö2à`x ê[¢ fJhØ /6904"«¹í m¾OÁclÅK3\ð¼îÊÝ$2,ûah¨yã3ÎàÇ#¤ÝtýªiÿµÕ»0eb½êw oÖ¤Ð.i`×ÃEo8>cv{ÑܨÃBçû>ó4=Gá¹Æ¤üon -'¾yýT'öÕu`?§{8àõð þ,²XÈhÕ¾ÊäÍÆdá%Dþ ½ÒQjQ9;tö}t(®¹ëé¦çòÜ¿Ã~7u7)Só!Ùþý$|yÑóc/ÅĤ¹FmZâToÚ®.ÙAt!åað2ì ÷98#ÿ[´M2\Ñv÷ðh»qÛ©¤=óµ9^lU¯µ2¹Rµ¸æX6l!á Da xçò&ab{SÑ8Ó-óH´®û\£+&$>LÆþzصñÝÑtn]ÃTÏE¤.<ÏhÃýËdýEþ®¶Üÿ®tj5LËÒÉöÄêCWo'E. 8KV%«ÿ[Éá/¯¬_î®þ{I¹ endstream endobj 42 0 obj 1757 endobj 40 0 obj << /Type /Page /Parent 34 0 R /Resources << /Font << /F0 6 0 R /F3 15 0 R >> /ProcSet 2 0 R >> /Contents 41 0 R >> endobj 44 0 obj << /Length 45 0 R /Filter /FlateDecode >> stream HWÛÛFýù~tlÞ÷%H²k¬Ýp0ðKlI´)RÛ$Gá~ýꮦD4F`âHÍêº:uê×ç2qÈTç¿?þxøé}(Ê §¯·aIÇJ<áYÊÏ'ñî·þ8f·ÅFB¡|ÿnª½Òx¯Õ¸×fx¿´øôxþúå ìã&Q)o2.ÂØ$kø¤m^tm_JKr_óoD õ og9+ø³¿UöB{¤\®³$òàxÆú-Ûø]krmíGOI,EG[A¤Ëçå¹P<EqàÎðWqÆÆ½)|ñ%Þù½8JYæÎÞÓ¦jÕ04ÝN4PbgP1Fz7Ó^wbî'Ñi]±ý6¦©µ@¡DE¾£4ô¯ºQºÍ4²xí·ÍRÙC2Ã3qÚãþº·âcq`¯^´hºQNµ¢ÖGÝÕº«=80îÁ¼ìOw_¤£sR×?³ë´ÄÎð¹®Ï{ i}2Z¨í1"`lgñëOPýpeê!ñÓûغ!®i/±kû {Q¦QV;£ Î&ÂKd²Î@ Ä{Ó¯Á)@ÆGó¬à²PéQ#vá<N_á1Ω\WE1Ã(Jvþ¦=Rj^ô£hF@O(ÙªjtmZmN9nû»EàÑôøâ0U{ú¼ ´t~,ðãÕu³½Ãu_Ü)G]Ön]$93ɺIaºá_z¤ØÉoBmú\Al pªÍa8³FiãÂ)PÌ£K§´Es)nÂð]à\A¼ýívLâ ²àNøLýwÒø<ÖÍPX¶è¶kqìÑè|l43 ãíq&éÏAôa P(ö89äò/bÛ¨i˲Ó'yÁ3é*;T{×r &ÓéÁ¥ËåI5ã~;µ rj:=f^ëÅ{tóü£EéIÕ=Ú¤ÁnßÙ0g°ágݶëfý:Á=%jLéöã~vD]ÌSº£n¾EAHNüÐTWÄ«@¤>¯%[»¢an¦$(â×N »S¥,o6Í×ØÉ w n¸EÁ·§©/!eS?µ5ÌTO®¨U_kÏóÕ %d6{S#ħ×WøÖKä3O6<Ù¦!$öBu³ºG;ð(AäÊÏfö½ïkTM×ÍÛÞ½aÆÉ =þãvÔsÌèÿN!Fmo ·é'T½7ß½2Ë/0T¤9âçÒ ý¶C Pîb¦Zeîà_ ª$ñìÅUN]Ö*Õ }çZ9;p Ç+¶¦?@f=þÔÕd`«Ð\ExÿÜÀ1ú'½jxOl´ÀâäUôDSv]©Ç«àñëÛü~¯¾Y¼°ÉW N|ü(yõºV&(/~Ø÷'ËT4ئ®ÒilõHìç?¬ïÉ#öcÓ2d¤qäżªhÅüf ,{p=Õ¨©DFEmzæóßg"ÒÅ'9¬^êÊö½Ì\Ç ÈÄͲ&qÄ1/¼Z úxyÃ,ÕÜéG'¦Î)ÉH 4^²·[UQðC¬½,Lq7ØMÊÔ Íl¿;ûù= ]6Y&=2n1$º7Íä:ûë! ÒïkÍ8¤,IÆÿ>èäÇdÄ7ÒîÃ06ÑàYý7%ºæ vÑ,^ln6)#¾L&ê=IÓu¸ÔèÖ7&Mq½%9+ QÄ¥´8prÓJ¢Ë©v4ÓVpDV)3Ln¹C,´]Ö£ÑLذpî/qC ËHK9×À)Å ê4vÐÐe~þÜØ1íôqf÷[.ºøH}ô÷w йG{´Ú¢74eÕáØj·QXõuvu\~Yß8+ó´%ZpýNÑd²ý¹2çp¼*Ry·7W»Çrö"B`ÄÙø'$ØßªBcËeóý3Wø"ÕTj»6îg¹â*v)øÒïIqðVüaóìjef´vBù¬?Áªã´a<ºY¹\ézg§ßÒ¾·ªºCQÎyTV½1 HW]ë¶ýûø¸-¼HQ_éCq³Ím÷á¢ÀÍAä)[]Y4Dz÷×wÊ×v ¸wZrfÎ9óÝëC°´\±×¿=$ÿ öðÍçUË_7É2)e5{g!$<Ùã³Ùv³uìq&D,ضÞ*ݱÏZ¹ì}ê:öõOìõß¼`NbÉW¢&Ó2II´fÙWmõð®×~SVáõh[ÜÁK7ØÁ弤%hΫýXÁ¹_RG'¹äÑ\<'C¦![¿iMÌݺÑLËN±eQ.|<½Ñ2/+^R<ÝV9v2¶Ñ½ÓÌ¿9ªÛÁOo[á;ÍL¯_y4nͺö Þö¦õµ½_bOÖé3ïzJØOYtKò4Ü`|ÄðCð»¶Ñ®Ýé9±ZõxêÁêõx¼jûw5´ªwKo\d9OR2FøþÄeÇãx)QP|ryæ?ÔìñzÓ~q 7xý³G w88KD8÷8´|¡M;X·`ºeÙg¼ØýöñÅÔû4ó㹤òHòÊüTë5þÀÙ¦Wå6OÀ UAð¦G,0[p|e:Kw ·bÎ?pÍÆ´à)]~D²ÈÒ4P沿êFÕ"öpâz6kM\6! ;Ð ËxqbÊLz³ð{ç¸Tsd"uì;UëÅtÉÑôk=4ýÞîзîA¬; ;ìïáw´¤8dKDÆñXkÛnyCêL·.2ÏV yvefmÍß8DZ¿©FþnÝ_þÇj¤ÔáVîÕàÚúЩª§m<ýæ2?ã=ÉrA¢¢Í5äbyb{cÐuávpí5ÕéºÞ°ØÀ<,·RüÃoºüvâ wÚmÍ9! ®ªî2xø~Ág·T¹Ræ4!2;¤ ÝFæ "ʨ é¯Û¦X>¸ ½Y~_ fÔ§%¦%R£¬*îBéZ0ìÍ»4dÍ$6Üéûwaíå@zØøç^(Oy,ÇBĤÐìPú ã '±ëɲH#ˬZ¿i; f(}_¯~ÀÇܵo4+>9Êâ~~YS".«2*Qeõ覆H,Oª)Ô¿B6åòJµÈn~«ó÷ÇI³îw½SËeR%æ0tƼAÄ6IZÌ.E%Ï$Q£YwjÓöȨF±Ë]dk¾¹£Â5eEX"-f![ø ¬á/!þP;f6D˳¾òö¡Ëÿ÷\Å#,ó6 ®bw©XìVwæÐþuL× áDª®A%tÓÊù#zg2lRÄéµ×Ú; Õ¿C6§B #*Í$S¹¢yNý1©x[ ($u1Ö¸çHä°æ0x¹±ÕCõA&ñØ~éìGÞAÓ CGw%ÿùjcA^°øë@ôølúº;Xð"ÄgÇ`Ás>ïp%Ê òZ%åI«@ä2"¥ÊéÍæIÌÁAªu®¬¬Ñ¦wôÚüf¡Ûæ,ÎnÎ%\¹t)ae ÊCàøÓµ;&>ÓÃÌ PYpn:ísäÙÑT^¸·Õ³$/æ¨ QHât+J¬"´iC.'3µÚÆÔ¬Ó;¶B©S lïÚCõÔ ïk×ÖÆÞº´s§?4Ì|ìÄd}hcvûpI<õïÿzYÀQ]çg¿ ¼¡\@Ó©o5`é(ÂW û¸vul>Æ^o)IV /ÏÝLùÎ{T^5`__ÛÁÓekÏf,³#fó§ß©ßÈ9«ñ\T9åB³/5ÔL¯¶¼7 Ì]Ô)ÆGØ HWÙÛ8üÿCï[°ºókv110X$yàHDR²âýúí&)ÙòzdGuuUzµ]å>Çbíô§v°z~çCî¥ôuµò=?r|,`ÏEø<³ײ;©z·ïá3`¾ÏÖð¡.ö\4p'x¿J¯áeÓÀý°ýº Ò×ÇÀ÷eÓaævIZMýÐBEi&Å9ÁsÓ¦Af°á°+'BËß óÏ[ûIL!ðÄ-$+·£Âuµ§m¢A[ Ffø4fBè¥,bvâ_NÉCëvü(ÚA@%¼çGN¿ì?S¨5 ¹P£hdwmOè_êNý{ø~ñ ÿ8ÜP÷ÐKhäÐCÓk¤h»íFüoJT¢âE/Õ¹`5`1;^·?#rcÁ,Ù4¦öÆajó¶-Hez×?¾9@EVÔÝ 5`ÆÈ¦Ù Dú®¢J)´÷1v`yq>ùBÃÑù>=?ÖRÑ.=-ÚªØRûûÈo`À÷\!Þ£PÛ F+, ë¢)j(!MsÈ`¢©uËMlÃkÒìR²M¿í!'kél'üd\~¢l:¸XF £TßLc²+©5tꤦz0ÚÍØR®¢qô¤ùÁÃØ%hÅB¬/CÌ®^UCðbo²¾áâR:)AÃÏpp¶eÊÒÂwBL¯zä©ë¡ïû.¬v¾LPm]Ù¯FÜîÚûç3éù]xqÚûlá-t_çÍòzùI0 jÉñ}@e¢p±X)t½k]<¦ÓX¨=Ͱ#oû9;6t?Z¶$Êé,pTéoG:,ÿ°þÏÎ%Ý2ýý§ë(úÅa$]ßy*ðüùÄú|;96Oêè¦Iu9»äsn ]Ñ$ endstream endobj 51 0 obj 1527 endobj 49 0 obj << /Type /Page /Parent 34 0 R /Resources << /Font << /F0 6 0 R /F3 15 0 R >> /ProcSet 2 0 R >> /Contents 50 0 R >> endobj 6 0 obj << /Type /Font /Subtype /TrueType /Name /F0 /BaseFont /TimesNewRoman /FirstChar 32 /LastChar 255 /Widths [ 250 333 408 500 500 833 778 180 333 333 500 564 250 333 250 278 500 500 500 500 500 500 500 500 500 500 278 278 564 564 564 444 921 722 667 667 722 611 556 722 722 333 389 722 611 889 722 722 556 722 667 556 611 722 722 944 722 722 611 333 278 333 469 500 333 444 500 444 500 444 333 500 500 278 278 500 278 778 500 500 500 500 333 389 278 500 500 722 500 500 444 480 200 480 541 778 500 778 333 500 444 1000 500 500 333 1000 556 333 889 778 611 778 778 333 333 444 444 350 500 1000 333 980 389 333 722 778 444 722 250 333 500 500 500 500 200 500 333 760 276 500 564 333 760 500 400 549 300 300 333 576 453 250 333 300 310 500 750 750 750 444 722 722 722 722 722 722 889 667 611 611 611 611 333 333 333 333 722 722 722 722 722 722 722 564 722 722 722 722 722 722 556 500 444 444 444 444 444 444 667 444 444 444 444 444 278 278 278 278 500 500 500 500 500 500 500 549 500 500 500 500 500 500 500 500 ] /Encoding /WinAnsiEncoding /FontDescriptor 7 0 R >> endobj 7 0 obj << /Type /FontDescriptor /FontName /TimesNewRoman /Flags 34 /FontBBox [ -250 -216 1158 1000 ] /MissingWidth 321 /StemV 73 /StemH 73 /ItalicAngle 0 /CapHeight 891 /XHeight 446 /Ascent 891 /Descent -216 /Leading 149 /MaxWidth 965 /AvgWidth 401 >> endobj 8 0 obj << /Type /Font /Subtype /TrueType /Name /F1 /BaseFont /Arial,Bold /FirstChar 32 /LastChar 255 /Widths [ 278 333 474 556 556 889 722 238 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 333 333 584 584 584 611 975 722 722 722 722 667 611 778 722 278 556 722 611 833 722 778 667 778 722 667 611 722 667 944 667 667 611 333 278 333 584 556 333 556 611 556 611 556 333 611 611 278 278 556 278 889 611 611 611 611 389 556 333 611 556 778 556 556 500 389 280 389 584 750 556 750 278 556 500 1000 556 556 333 1000 667 333 1000 750 611 750 750 278 278 500 500 350 556 1000 333 1000 556 333 944 750 500 667 278 333 556 556 556 556 280 556 333 737 370 556 584 333 737 552 400 549 333 333 333 576 556 278 333 333 365 556 834 834 834 611 722 722 722 722 722 722 1000 722 667 667 667 667 278 278 278 278 722 722 778 778 778 778 778 584 778 722 722 722 722 667 667 611 556 556 556 556 556 556 889 556 556 556 556 556 278 278 278 278 611 611 611 611 611 611 611 549 611 611 611 611 611 556 611 556 ] /Encoding /WinAnsiEncoding /FontDescriptor 9 0 R >> endobj 9 0 obj << /Type /FontDescriptor /FontName /Arial,Bold /Flags 16416 /FontBBox [ -250 -212 1142 1000 ] /MissingWidth 317 /StemV 153 /StemH 153 /ItalicAngle 0 /CapHeight 905 /XHeight 453 /Ascent 905 /Descent -212 /Leading 150 /MaxWidth 952 /AvgWidth 479 >> endobj 12 0 obj << /Type /Font /Subtype /TrueType /Name /F2 /BaseFont /Arial,BoldItalic /FirstChar 32 /LastChar 255 /Widths [ 278 333 474 556 556 889 722 238 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 333 333 584 584 584 611 975 722 722 722 722 667 611 778 722 278 556 722 611 833 722 778 667 778 722 667 611 722 667 944 667 667 611 333 278 333 584 556 333 556 611 556 611 556 333 611 611 278 278 556 278 889 611 611 611 611 389 556 333 611 556 778 556 556 500 389 280 389 584 750 556 750 278 556 500 1000 556 556 333 1000 667 333 1000 750 611 750 750 278 278 500 500 350 556 1000 333 1000 556 333 944 750 500 667 278 333 556 556 556 556 280 556 333 737 370 556 584 333 737 552 400 549 333 333 333 576 556 278 333 333 365 556 834 834 834 611 722 722 722 722 722 722 1000 722 667 667 667 667 278 278 278 278 722 722 778 778 778 778 778 584 778 722 722 722 722 667 667 611 556 556 556 556 556 556 889 556 556 556 556 556 278 278 278 278 611 611 611 611 611 611 611 549 611 611 611 611 611 556 611 556 ] /Encoding /WinAnsiEncoding /FontDescriptor 13 0 R >> endobj 13 0 obj << /Type /FontDescriptor /FontName /Arial,BoldItalic /Flags 16480 /FontBBox [ -250 -212 1174 1000 ] /MissingWidth 326 /StemV 153 /StemH 153 /ItalicAngle -11 /CapHeight 905 /XHeight 453 /Ascent 905 /Descent -212 /Leading 150 /MaxWidth 978 /AvgWidth 479 >> endobj 15 0 obj << /Type /Font /Subtype /TrueType /Name /F3 /BaseFont /TimesNewRoman,Italic /FirstChar 32 /LastChar 255 /Widths [ 250 333 420 500 500 833 778 214 333 333 500 675 250 333 250 278 500 500 500 500 500 500 500 500 500 500 333 333 675 675 675 500 920 611 611 667 722 611 611 722 722 333 444 667 556 833 667 722 611 722 611 500 556 722 611 833 611 556 556 389 278 389 422 500 333 500 500 444 500 444 278 500 500 278 278 444 278 722 500 500 500 500 389 389 278 500 444 667 444 444 389 400 275 400 541 778 500 778 333 500 556 889 500 500 333 1000 500 333 944 778 556 778 778 333 333 556 556 350 500 889 333 980 389 333 667 778 389 556 250 389 500 500 500 500 275 500 333 760 276 500 675 333 760 500 400 549 300 300 333 576 523 250 333 300 310 500 750 750 750 500 611 611 611 611 611 611 889 667 611 611 611 611 333 333 333 333 722 667 722 722 722 722 722 675 722 722 722 722 722 556 611 500 500 500 500 500 500 500 667 444 444 444 444 444 278 278 278 278 500 500 500 500 500 500 500 549 500 500 500 500 500 444 500 444 ] /Encoding /WinAnsiEncoding /FontDescriptor 16 0 R >> endobj 16 0 obj << /Type /FontDescriptor /FontName /TimesNewRoman,Italic /Flags 98 /FontBBox [ -250 -216 1165 1000 ] /MissingWidth 378 /StemV 73 /StemH 73 /ItalicAngle -11 /CapHeight 891 /XHeight 446 /Ascent 891 /Descent -216 /Leading 149 /MaxWidth 971 /AvgWidth 402 >> endobj 25 0 obj << /Type /Font /Subtype /TrueType /Name /F4 /BaseFont /Arial /FirstChar 32 /LastChar 255 /Widths [ 278 278 355 556 556 889 667 191 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 333 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500 278 556 500 722 500 500 500 334 260 334 584 750 556 750 222 556 333 1000 556 556 333 1000 667 333 1000 750 611 750 750 222 222 333 333 350 556 1000 333 1000 500 333 944 750 500 667 278 333 556 556 556 556 260 556 333 737 370 556 584 333 737 552 400 549 333 333 333 576 537 278 333 333 365 556 834 834 834 611 667 667 667 667 667 667 1000 722 667 667 667 667 278 278 278 278 722 722 778 778 778 778 778 584 778 722 722 722 722 667 667 611 556 556 556 556 556 556 889 500 556 556 556 556 278 278 278 278 556 556 556 556 556 556 556 549 611 556 556 556 556 500 556 500 ] /Encoding /WinAnsiEncoding /FontDescriptor 26 0 R >> endobj 26 0 obj << /Type /FontDescriptor /FontName /Arial /Flags 32 /FontBBox [ -250 -212 1220 1000 ] /MissingWidth 278 /StemV 80 /StemH 80 /ItalicAngle 0 /CapHeight 905 /XHeight 453 /Ascent 905 /Descent -212 /Leading 150 /MaxWidth 1017 /AvgWidth 441 >> endobj 2 0 obj [ /PDF /Text ] endobj 5 0 obj << /Kids [4 0 R 14 0 R 19 0 R 22 0 R 27 0 R 30 0 R ] /Count 6 /Type /Pages /Parent 52 0 R >> endobj 34 0 obj << /Kids [33 0 R 37 0 R 40 0 R 43 0 R 46 0 R 49 0 R ] /Count 6 /Type /Pages /Parent 52 0 R >> endobj 52 0 obj << /Kids [5 0 R 34 0 R ] /Count 12 /Type /Pages /MediaBox [ 0 0 612 792 ] >> endobj 1 0 obj << /Creator <FEFF0057006F0072006B0069006E0067004500660066006500630074006900760065006C00790057006900740068004C006500670061006300790043006F00640065002E0064006F00630020002D0020004D006900630072006F0073006F0066007400200057006F00720064> /CreationDate (D:20020409142517) /Title <FEFF0057006F0072006B0069006E0067004500660066006500630074006900760065006C00790057006900740068004C006500670061006300790043006F00640065002E005000440046> /Author <FEFF00410064006D0069006E006900730074007200610074006F0072> /Producer (Acrobat PDFWriter 4.05 for Windows NT) >> endobj 3 0 obj << /Pages 52 0 R /Type /Catalog >> endobj xref 0 53 0000000000 65535 f 0000033817 00000 n 0000033475 00000 n 0000034374 00000 n 0000002312 00000 n 0000033506 00000 n 0000026700 00000 n 0000027789 00000 n 0000028049 00000 n 0000029138 00000 n 0000000019 00000 n 0000002291 00000 n 0000029400 00000 n 0000030497 00000 n 00 00004945 00000 n 0000030768 00000 n 0000031864 00000 n 0000002454 00000 n 0000004924 00000 n 0000007278 00000 n 0000005089 00000 n 0000007257 00000 n 0000010553 00000 n 0000007410 00000 n 0000010532 00000 n 0000032134 00000 n 0000033221 00000 n 0000012807 00000 n 0000010696 00000 n 0000012786 00000 n 0000014459 00000 n 0000012962 00000 n 0000014438 00000 n 0000016009 00000 n 0000033614 00000 n 0000014579 00000 n 0000015988 00000 n 0000017512 00000 n 0000016141 00000 n 0000017491 00000 n 0000019489 00000 n 0000017633 00000 n 0000019468 00000 n 0000022114 00000 n 0000019622 00000 n 0000022093 00000 n 0000024796 00000 n 0000022258 00000 n 0000024775 00000 n 0000026567 00000 n 0000024941 00000 n 0000026546 00000 n 0000033724 00000 n trailer << /Size 53 /Root 3 0 R /Info 1 0 R /ID [<f3c8b805362eb046db68430d3945fe13><f3c8b805362eb046db68430d3945fe13>] >> startxref 34424 %%EOF ... [truncated message content] |
From: Steve F. <sm...@us...> - 2002-08-12 23:07:03
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/related In directory usw-pr-cvs1:/tmp/cvs-serv8198/doc/related Log Message: Directory /cvsroot/mockobjects/no-stone-unturned/doc/related added to the repository |
From: Steve F. <sm...@us...> - 2002-08-11 14:09:32
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv30187/doc/xdocs Modified Files: how_mocks_happened.xml Log Message: more rework Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- how_mocks_happened.xml 10 Aug 2002 23:43:41 -0000 1.4 +++ how_mocks_happened.xml 11 Aug 2002 14:09:28 -0000 1.5 @@ -49,7 +49,7 @@ <para> This test tells us that the robot is in the same place after the test as it was before the test. That's an essential requirement, but it's not enough. We can't be sure that the robot hasn't - trundled all the way around the warehouse before returning; we need to know that the Robot hasn't + trundled all the way around the warehouse before returning; we need to know that the robot hasn't moved. How about if the robot were to store the route it takes each time we call <methodname>goto()</methodname>? We could retrieve the route and make sure it's valid. For example: </para> @@ -60,14 +60,14 @@ Robot robot = new Robot(); robot.setCurrentPosition(POSITION); - robot.goTo(POSITION); + robot.goto(POSITION); <emphasis>assertEquals("Should be empty", 0, robot.getRecentMoveRequests().size());</emphasis> assertEquals("Should be same", POSITION, robot.getPosition()); }</programlisting> <para> - So far we've specified that the Robot will not move if asked to go to the same place. We've + So far we've specified that the robot will end up in the same place if that's where we ask it to go. We've also specified that it will store the route from it's most recent move, and something about the programming interface that it should support. Our next test is to move one point on the grid: </para> @@ -87,10 +87,17 @@ new MoveRequest(1, MoveRequest.SOUTH), moves.get(1)); }</programlisting> + <sidebar> + <para> + Of course, you've noticed that we haven't said anything about the layout of the warehouse. We'll + assume for now that it's regular and hard-coded into the Robot. + </para> + </sidebar> + <para> Now we've also specified the simplest routing scheme, for moves to adjacent squares, and that we're describing each leg of the route with a <classname>MoveRequest</classname> object. We carry on and - pretty soon we're moving the Robot all over the building, for example: + pretty soon we're moving the robot all over the building, for example: </para> <programlisting> @@ -111,33 +118,32 @@ to keep the tests legible. For example, <methodname>makeExpectedLongWayMoves()</methodname> returns a list of the moves we expect the robot to make for this destination. </para> + </section> <!-- Simple Unit Tests --> - <sidebar> - <para> - Of course, you've noticed that we haven't said anything about the layout of the warehouse. We'll - assume for now that it's regular and hard-coded into the Robot. - </para> - </sidebar> + <section> + <title>What's wrong with this?</title> <para> - There are difficulties with this approach to testing. First, tests like this are effectively - small-scale integration tests, they set up pre-conditions and test post-conditions but they have no - access to the code while it is running. If this test failed, the error report would say something like: + Tests like this are effectively small-scale integration tests, they set up pre-conditions and + test post-conditions. There are advantages to this technique because test failures can reveal interesting + dependencies between classes and help to drive refactorings. The disadvantage is that the tests + has no access to the code while it is running. If our last test failed, the error report would + say something like: </para> <screen> There was 1 failure: 1) testMoveALongWay(test.nostone.RobotTest)junit.framework.AssertionFailedError: Should be same moves:<[10, South; 3, East; 7, South; &elipsis;> but was: <[10, South; 3, East; 7, South; &elipsis;> - at test.notstone.RobotTest.testMoveALongWay() + at test.nostone.RobotTest.testMoveALongWay() FAILURES!!!</screen> <para> We will have to step through the code to find the problem because the assertions have - been made <emphasis>after</emphasis> the call has finished. Even so, the robot is a relatively simple example. - Some of us tried to do this with financial mathematics and it's very painful when a calculation fails. - A test failure usually meant stepping carefully through the code with an open spreadsheet nearby to check - the values. + been made <emphasis>after</emphasis> the call has finished. It could be worse, the robot is a relatively + simple example. Some of us tried to do this with financial mathematics and it's very painful when a + calculation fails. A test failure usually meant stepping carefully through the code with an open spreadsheet + nearby to check the values. </para> <para> @@ -157,15 +163,15 @@ </para> <para> - Is there a better way? Can we find a technique that's less intrusive, that doesn't require the Robot - to hang on to unnecessary values? Can we have more helpful testfailures—that will fail faster, - like we know we're supposed to? What would happen if we really believed in the object design - maxim <quote>Tell, don't ask</quote>? + Is there a better way? Can we find a technique that's less intrusive, that doesn't require the robot + to hang on to unnecessary values? Can we have more helpful test failures that will fail faster, + like we know we're supposed to? What would happen if we really believed in the object maxim + <quote>Tell, don't ask</quote>? </para> - </section> <!-- Simple Unit Tests --> + </section> <!-- what's wrong with this --> <section> - <title>Breaking the Robot apart</title> + <title>Breaking apart the Robot</title> <para> Let's stop and think for a moment. Our robot is actually doing two things when we ask it to move @@ -173,19 +179,19 @@ <emphasis>and</emphasis> it has to move along the route it has chosen. Those activities are separate, even if they might happen at the same time. If we break out these responsibilities into two objects, we can also separate the testing that goes with them. We can test that the route planning - object creates a suitable route and that the object that moves the Robot follows that route correctly. + object creates a suitable route and that the Robot moving object follows that route correctly. + </para> + <para> We'll start with the "Robot Moving Object", what would be a good name for such a component? How about <emphasis>Motor</emphasis>? If we leave the route planning in the <classname>Robot</classname> object, we can intercept the requests that the <classname>Robot</classname> makes to its - <classname>Motor</classname>, which means that we can see inside without holding on to the route data. - </para> - - <para> - We'll start by defining an interface for <classname>Motor</classname>. We know that - there must be some kind of request, which we'll call <methodname>move()</methodname>, that takes some - kind of move request as a parameter. We don't yet know what's in that request, so we'll define an empty - <classname>MoveRequest</classname> interface as a placeholder to get us through the compiler. In dynamic - libraries, such as Smalltalk and Python, we don't even need to do that. + <classname>Motor</classname>, which means that we would be able to see inside the Robot without holding + on to the route data. + First we define an interface for <classname>Motor</classname>. We know that there must be some kind of + request, which we'll call <methodname>move()</methodname>, that takes some kind of move request as a parameter. + We don't yet know what's in that request, so we'll define an empty <classname>MoveRequest</classname> + interface as a placeholder to get us through the compiler. (Of course, in dynamic languages, such as Smalltalk + and Python, we don't even need to do that.) </para> <programlisting> @@ -193,49 +199,68 @@ void move(MoveRequest request); }</programlisting> - <comment>From here!</comment> - <para> - Now I need to initialise a Robot with a Motor when I create it. - Because I want to intercept the interaction between the Robot and its Motor - I cannot let the Robot instantiate its own Motor; there would be no way to - then intercept the Robot's movement requests. That leads me to pass a - Motor instance to the Robot's constructor. + Now that we've separated out the motor from the robot, where does the <classname>Robot</classname> object get its + instance of the <classname>Motor</classname> class? We don't yet know what a real <classname>Motor</classname> + looks like, so we can't just create a default instance. We could, however, pass in an temporary implementation + during the test, so we'll add a <classname>Motor</classname> to the <classname>Robot</classname>'s constructor. </para> <para> - I can now write my tests to create a Robot with an implementation - of the <classname>Motor</classname> interface, that watches what's - happening in the Robot, and complains as soon as something goes wrong. - In fact, I will do this right now, before I start thinking about writing a - real implementation of the Motor interface, so that I know my Robot - implementation still works despite the extensive refactorings I have - performed. The first test is now: + Now we have to decide what the test implementation will do. In this test, we want to be sure that the + <classname>Robot</classname> stays in place, so the test <classname>Motor</classname> should simply + fail if it receives any requests to move. We can write this test now, before we know anything else about + the system, which means that we have locked down a little piece of the specification. We can be sure that, + however complex our routing code gets, the <classname>Robot</classname> will not move if asked to go to + its current position. The new version of the test is: </para> - <programlisting>public void testGotoSamePlace() { - final Position POSITION = new Position(0, 0); - robot.setCurrentPosition(POSITION); + <programlisting> +public void testGotoSamePlace() { + final Position POSITION = new Position(1, 1); - Motor mockMotor = new Motor() { + <emphasis>Motor mockMotor = new Motor() { public void move(MoveRequest request) { - fail("There should be no moves in this test"); + fail("Should be no moves"); } }; - robot.setMotor(mockMotor); + Robot robot = new Robot(mockMotor);</emphasis> + robot.setCurrentPosition(POSITION); robot.goTo(POSITION); assertEquals("Should be same", POSITION, robot.getPosition()); }</programlisting> -<para>In this test, if there is a bug in the Robot code and the Motor -gets requested to move, the mock implementation of -<function>move()</function> -will fail immediately and stop the test; I no longer need to ask the Robot -where it's been.</para> + <para> + Now if there's a bug in the robot routing code that asks the motor to move, the test will fail at the + point that the request is made. The error report might look like: + </para> + + <screen> +There was 1 failure: +1) testGotoSamePlace(test.nostone.RobotTest)junit.framework.AssertionFailedError: + Should be no moves + at test.nostone.RobotTest.testGotoSamePlace(test.nostone.RobotTest); + at tdd.nostone.RobotTest$Motor.move() + at tdd.nostone.Robot.requestNextMove() + at tdd.nostone.Robot.chooseBestPath() + at tdd.nostone.Robot.arriveAtJunction() + &elipsis; +FAILURES!!!</screen> + + <para> + which gives a much clearer view of where the error became visible. If finding the problem turns out to be + harder, we can trap the <classname>junit.framework.AssertionFailedError</classname> in the development + tool to bring up the debugger. Then we can explore the program state at the time of the failure, without + having to step through from the beginning of the test. + </para> + + + <!-- TODO --> + -<para>Now I know that my Robot class works I can write a real implementation +<para>Now I know that my Robot class works I can write a real implementation of the Motor interface:</para> <programlisting> |
From: Steve F. <sm...@us...> - 2002-08-10 23:43:45
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv25267/doc/xdocs Modified Files: htmlbook.xsl doc-book.xml introduction.xml testing_guis_1.xml how_mocks_happened.xml random.xml htmlbook.css extra_entities.xml Added Files: preface.xml Log Message: Restructuring Rewriting within how_mocks_happened. --- NEW FILE: preface.xml --- <preface> <title>Preface</title> <section> <title>Why I wrote this book</title> <para> My original motivation for this book was that I got tired of explaining what a group of us in London were doing with test-driven development. We had discovered a style of programming that delivered well-structured, reliable code quickly and that seemed to reinforce what we had learned over the years to be good coding habits. Over and over again, we found that developers thought that what we were doing was unnecessary, or too complicated, or too hard to understand, or just weird until we sat down and programmed through some examples with them. This seemed like a very slow way to spread a useful idea, so I started writing to try to speed things up. </para> <para> Since then, I have had my motivation strengthened by the experience of taking over development of a couple of systems written elsewhere. Both systems worked and were developed under time pressure, so clearly the programmers had got something right. Both systems, however, were brittle, with much repetition and needlessly complex logic. One, in particular, had accrued over years and noone had had the time or, perhaps, the inclination to clean up behind themselves. Worse, there were no automated tests (of course, the documentation was out of date) so I could only make changes to the working system by being very cautious and, where possible, retrofitting tests. The contrast with the experience of raw confidence when working with a fully test-driven codebase was so unpleasant that I resolved to do <emphasis>something</emphasis>, however small, to improve the situation. </para> <para> My intentions are entirely selfish, I want to increase the probability by a notch that the next system I have to deal with has been written test-first so that I don't have to do this again. I have an unselfish intention as well, which is that no-one in the next generation of programmers should have to go through the Debug Hell that we old fogeys accept as part of the process of writing software. Of course, it <emphasis>is</emphasis> possible to fall off the rails with Test-Driven Development, but it's a little bit harder. Our generation will know that we've really succeeded when no-one can quite remember why we spent so much time building debugging tools. I'm not holding my breath, but it's a target that's worth aspiring to. </para> </section> <section> <title>Who this book is for</title> <para> As with most authors, I'm writing this book for people like me who happen not have seen some of the same things. It's for working programmers who want to learn more about how to do Test-Driven Development in practice. I'm assuming that: </para> <itemizedlist spacing="compact"> <listitem><para> you already think that Test-Driven Development is a good idea, or at least worth investigating; </para></listitem> <listitem><para> you know something about the basics, such as refactoring and the JUnit framework; and, </para></listitem> <listitem><para> if you're not currently working in Java, then you can figure out how to apply these concepts to whatever environment you're working in. </para></listitem> </itemizedlist> </section> <section status="todo"> <title>Organisation</title> &todo; </section> <section status="todo"> <title>Acknowledgements</title> &todo; </section> </preface> Index: htmlbook.xsl =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.xsl,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- htmlbook.xsl 10 Aug 2002 20:56:50 -0000 1.7 +++ htmlbook.xsl 10 Aug 2002 23:43:41 -0000 1.8 @@ -29,6 +29,22 @@ <xsl:call-template name="inline.gui"/> </xsl:template> + <xsl:template name="inline.comment"> + <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="comment"><xsl:copy-of select="$content"/></span> + </xsl:template> + + <xsl:template match="comment"> + <xsl:call-template name="inline.comment"/> + </xsl:template> + <xsl:param name="generate.toc"> /book toc /book/part toc Index: doc-book.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/doc-book.xml,v retrieving revision 1.8 retrieving revision 1.9 diff -u -r1.8 -r1.9 --- doc-book.xml 10 Aug 2002 18:11:00 -0000 1.8 +++ doc-book.xml 10 Aug 2002 23:43:41 -0000 1.9 @@ -3,8 +3,10 @@ <!ENTITY % docbook SYSTEM "file://@docpath@/dtd/docbookx.dtd"> <!ENTITY % extra_entities SYSTEM "file://@docpath@/extra_entities.xml"> + <!ENTITY preface SYSTEM "file://@docpath@/preface.xml"> + <!ENTITY part_introduction SYSTEM "file://@docpath@/introduction.xml"> - <!ENTITY chp_how_mocks_happened SYSTEM "file://@docpath@/how_mocks_happened.xml"> + <!ENTITY how_mocks_happened SYSTEM "file://@docpath@/how_mocks_happened.xml"> <!ENTITY chp_servlets_1 SYSTEM "file://@docpath@/servlets_1.xml"> <!ENTITY chp_servlets_2 SYSTEM "file://@docpath@/servlets_2.xml"> @@ -19,17 +21,15 @@ %extra_entities; ]> -<book> +<book status="draft"> <title>No Stone Unturned</title> <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> @@ -39,45 +39,72 @@ </copyright> </bookinfo> - &part_introduction; + &preface; - <part> - <title>Larger examples</title> + &part_introduction; + <part status="draft"> + <title>A longer example</title> &chp_servlets_1; &chp_servlets_2; &chp_servlets_3; + </part> + + <part status="draft"> + <title>Working with third party libraries</title> &chp_jdbc_testfirst; </part> &part_testing_guis; - <part> + <part status="todo"> <title>Living with Unit Tests</title> - <chapter><title>Test organisation</title></chapter> - <chapter><title>Test smells</title></chapter> - <chapter><title>Retrofitting unit tests</title></chapter> + <chapter status="todo"> + <title>Test organisation</title> + &todo; + </chapter> + <chapter status="todo"> + <title>Test smells</title> + &todo; + </chapter> + <chapter status="todo"> + <title>Retrofitting unit tests</title> + &todo; + </chapter> </part> <part><title>Some other languages</title> - <chapter><title>C++</title> - <para>with Workshare?</para> + <chapter> + <title>C++</title> + &todo; + <comment><para>with Workshare?</para></comment> </chapter> - <chapter><title>Dynamic and metaprogramming</title></chapter> - <chapter><title>bash</title></chapter> + <chapter> + <title>Dynamic and metaprogramming</title> + &todo; + </chapter> + <chapter> + <title>bash</title> + &todo; + </chapter> </part> <part> - <title>Other patterns</title> + <title>Other cases</title> &chp_random; </part> - <part> + <part status="todo"> <title>Closing</title> + &todo; </part> + <appendix status="todo"> + <title>The Expectation Library</title> + &todo; + </appendix> ¬es; </book> Index: introduction.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/introduction.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- introduction.xml 9 Aug 2002 23:47:43 -0000 1.1 +++ introduction.xml 10 Aug 2002 23:43:41 -0000 1.2 @@ -1,90 +1,31 @@ - <part> - <title>Introducing Test-Driven Development</title> +<part> + <title>Introducing Test-Driven Development</title> - <chapter><title>Introduction</title> - <section> - <title>Why I wrote this book</title> - - <para> - My original motivation for this book was that I got tired of - explaining what a group of us in London were doing with test-driven - development. We had discovered a style of programming that delivered - well-structured, reliable code quickly and that seemed to reinforce - what we had learned over the years to be good coding habits. Over and - over again, we found that developers thought that what we were doing - was unnecessary, or too complicated, or too hard to understand, or - just weird until we sat down and programmed through some examples with - them. This seemed like a very slow way to spread a useful idea, so I - started writing to try to speed things up. - </para> - - <para> - Since then, I have had my motivation strengthened by the experience - of taking over development of a couple of systems written - elsewhere. Both systems worked and were developed under time pressure, - so clearly the programmers had got something right. Both systems, - however, were brittle, with much repetition and needlessly complex - logic. One, in particular, had accrued over years and noone had had - the time or, perhaps, the inclination to clean up behind - themselves. Worse, there were no automated tests (of course, the - documentation was out of date) so I could only make changes to the - working system by being very cautious and, where possible, - retrofitting tests. The contrast with the experience of raw confidence - when working with a fully test-driven codebase was so unpleasant that - I resolved to do <emphasis>something</emphasis>, however small, to improve the - situation. - </para> - - <para> - My intentions are entirely selfish, I want to increase the - probability by a notch that the next system I have to deal with has - been written test-first so that I don't have to do this again. I have - an unselfish intention as well, which is that no-one in the next - generation of programmers should have to go through the Debug Hell - that we old fogeys accept as part of the process of writing software. - It <emphasis>is</emphasis> possible to fall off the rails with Test-Driven - Development, but it's so rare that we would have to make an effort to - preserve the skills when Test-Driven Development becomes the standard - way to write code. - </para> - </section> - - <section> - <title>Who this book is for</title> - - <para> - As with most authors, I'm writing this book for people like me who - happen not have seen some of the same things. It's for working - programmers who want to learn more about how to do Test-Driven - Development in practice. I'm assuming that: - </para> - - <itemizedlist spacing="compact"> - <listitem><para> - you already think that Test-Driven Development is a good idea, or - at least worth investigating; - </para></listitem> - <listitem><para> - you know something about the basics, such as refactoring and the JUnit framework; and, - </para></listitem> - <listitem><para> - if you're not currently working in Java, then you can figure out how to apply these concepts - to whatever environment you're working in. - </para></listitem> - </itemizedlist> - </section> - - <section><title>Organisation</title></section> - </chapter> - - <chapter><title>Test-Driven Development</title> - <section><title>Introduction and simple example</title></section> - <section><title>A quick tour of JUnit</title> </section> - </chapter> - - &chp_how_mocks_happened; - - <chapter><title>Expectations</title> - </chapter> - </part> + <preface status="draft"> + <title>Preface</title> + + <para> + In this part, we introduce Test-Driven Development. We only give a brief refresher on the basics, because there + are other books that are more suitable as an introduction to the technique. We do, however, spend some time + introducing Mock Objects because that's how a group of us have been developing for several years and we like + what it does to our code. Let's get down to business. + </para> + </preface> + + <chapter status="todo"> + <title>Test-Driven Development</title> + + <section> + <title>Introduction and simple example</title> + &todo; + </section> + <section> + <title>A quick tour of JUnit</title> + &todo; + </section> + </chapter> + + &how_mocks_happened; + +</part> Index: testing_guis_1.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/testing_guis_1.xml,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- testing_guis_1.xml 5 Aug 2002 01:30:48 -0000 1.5 +++ testing_guis_1.xml 10 Aug 2002 23:43:41 -0000 1.6 @@ -2,17 +2,18 @@ <title>Developing Graphical Applications</title> <partintro> + <title>Introduction</title> <para> - It <emphasis>is</emphasis> possible to develop graphical applications test-first. It's not easy, because - graphical user interfaces have to respond to people (who are less predictable and linear than we'd - like) and so tend to have lots of global state that's hard to test in isolation. That, and the variable - quality of some popular graphical toolkits, means that we have to think a bit harder we do for non-graphical - applications. It took a while for the test-driven development community to figure out what to do, for a - long time the answer was to make the presentation layer as thin as possible and to defer everything down - to a business layer. That's probably why there are no tests for the graphical test runners for - <application>JUnit</application>. This is still good advice but it doesn't feel good enough, given - how much we say we believe in the benefits of writing the tests first. Now we have a better handle on how to - work with graphical environments (or at least some of them). Let's see something working. + It <emphasis>is</emphasis> possible to develop graphical applications test-first. It's not easy, because + graphical user interfaces have to respond to people (who are less predictable and linear than we'd + like) and so tend to have lots of global state that's hard to test in isolation. That, and the variable + quality of some popular graphical toolkits, means that we have to think a bit harder we do for non-graphical + applications. It took a while for the test-driven development community to figure out what to do, for a + long time the answer was to make the presentation layer as thin as possible and to defer everything down + to a business layer. That's probably why there are no tests for the graphical test runners for + <application>JUnit</application>. This is still good advice but it doesn't feel good enough, given + how much we say we believe in the benefits of writing the tests first. Now we have a better handle on how to + work with graphical environments (or at least some of them). Let's see something working. </para> </partintro> @@ -559,18 +560,17 @@ </para> </sect1> <!-- What have we learned? --> - </chapter> <!-- Second test --> + <chapter status="todo"> + <title>More test cases</title> + &todo; + </chapter> + <appendix id="findNamedComponent" status="todo"> <title>Finding GUI components</title> + &todo; </appendix> - <appendix status="todo"> - <title>To do.</title> - <simplelist type="vert"> - <member>redo GUI images on the same platform.</member> - </simplelist> - </appendix> </part> Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- how_mocks_happened.xml 10 Aug 2002 20:56:50 -0000 1.3 +++ how_mocks_happened.xml 10 Aug 2002 23:43:41 -0000 1.4 @@ -1,12 +1,13 @@ -<chapter> +<chapter status="draft"> <title>How Mock Objects Happened</title> <section> <title>Introduction</title> + <para> Mock Objects is a development technique that lets you unit test classes that you didn't think - you could <emphasis>and</emphasis> helps you write better code whilst doing so. This chapter - uses a simple example to show a compressed history of how Mock Objects was discovered by + you could <emphasis>and</emphasis> helps you write better code while doing so. In this chapter + we use a simple example to show a compressed history of how Mock Objects was discovered by refactoring from conventional unit tests and why they're useful. </para> @@ -21,24 +22,23 @@ <section> <title>Simple Unit Tests</title> + <para>Let's get straight into an example.</para> + <para> - Let's dive straight into a worked example. We're writing software to direct robots through a - warehouse. Given a starting point on a grid, it will find a route through the stacks to a - specified destination. How can we test this? The standard approach is to write some unit tests - that create a robot, call some methods on it, and then check that the resulting state is what we expect. - Our tests will have to tell us, for example, that the robot arrives where it was directed to go and - does not hit anything on the way. A easy test to start with is to check that a robot does not move - if it is told to go to its current location: + We're writing software to direct a robot through a warehouse. Given a starting point on a grid, it will + find a route through the stacks to its destination. How can we test this? The standard approach + is to write some unit tests that create a robot, call some methods on it, and then check that it has + done what we asked. Our tests will have to tell us, for example, that the robot arrives + where it was directed to go and does not hit anything on the way. We'll start with the easiest test + we can think of, which is that the robot does not move if it is told to go to its current location: </para> <programlisting> public class TestRobot { - &elipsis; - public void setUp() { - robot = new Robot(); - } public void testGotoSamePlace() { final Position POSITION = new Position(1, 1); + + Robot robot = new Robot(); robot.setCurrentPosition(POSITION); robot.goTo(POSITION); @@ -47,27 +47,36 @@ }</programlisting> <para> - This tells us that the robot thinks that it has arrived at the right place, but it doesn't tell us anything - about how it got there; it might have trundled all the way round the warehouse before returning to - its original location. We would like to know that the Robot hasn't moved. One option would - be to store and retrieve the route it took after each <function>goto()</function>. For example: + This test tells us that the robot is in the same place after the test as it was before the test. + That's an essential requirement, but it's not enough. We can't be sure that the robot hasn't + trundled all the way around the warehouse before returning; we need to know that the Robot hasn't + moved. How about if the robot were to store the route it takes each time we call + <methodname>goto()</methodname>? We could retrieve the route and make sure it's valid. For example: </para> <programlisting> public void testGotoSamePlace() { final Position POSITION = new Position(0, 0); + + Robot robot = new Robot(); robot.setCurrentPosition(POSITION); robot.goTo(POSITION); - assertEquals("Should be empty", 0, robot.getRecentMoveRequests().size()); + <emphasis>assertEquals("Should be empty", 0, robot.getRecentMoveRequests().size());</emphasis> assertEquals("Should be same", POSITION, robot.getPosition()); }</programlisting> - <para>The test might be to move one point on the grid:</para> + <para> + So far we've specified that the Robot will not move if asked to go to the same place. We've + also specified that it will store the route from it's most recent move, and something about + the programming interface that it should support. Our next test is to move one point on the grid: + </para> <programlisting> public void testMoveOnePoint() { final Position DESTINATION = new Position(1, 0); + + Robot robot = new Robot(); robot.setCurrentPosition(new Position(0, 0)); robot.goto(DESTINATION); @@ -79,13 +88,16 @@ }</programlisting> <para> - As the tests become more complex, we pull some of the detail out into helper methods to make - the code more legible: + Now we've also specified the simplest routing scheme, for moves to adjacent squares, and that we're + describing each leg of the route with a <classname>MoveRequest</classname> object. We carry on and + pretty soon we're moving the Robot all over the building, for example: </para> <programlisting> public void testMoveALongWay() { final Position DESTINATION = new Position(34, 71); + + Robot robot = new Robot(); robot.setCurrentPosition(new Position(0, 0)); robot.goto(DESTINATION); @@ -95,45 +107,85 @@ }</programlisting> <para> - where <function>makeExpectedLongWayMoves()</function> returns a list of the - moves we expect the robot to make for this test. + As the tests have become more complex, we've pulled out some of the detail into helper methods + to keep the tests legible. For example, <methodname>makeExpectedLongWayMoves()</methodname> returns + a list of the moves we expect the robot to make for this destination. </para> + <sidebar> + <para> + Of course, you've noticed that we haven't said anything about the layout of the warehouse. We'll + assume for now that it's regular and hard-coded into the Robot. + </para> + </sidebar> + <para> - There are problems with this approach to testing. First, tests like this are effectively + There are difficulties with this approach to testing. First, tests like this are effectively small-scale integration tests, they set up pre-conditions and test post-conditions but they have no - access to the code while it is running. If one of these tests fail, we will have to step through the - code to find the problem because the assertions have been made <emphasis>after</emphasis> the call has finished. + access to the code while it is running. If this test failed, the error report would say something like: + </para> + + <screen> +There was 1 failure: +1) testMoveALongWay(test.nostone.RobotTest)junit.framework.AssertionFailedError: + Should be same moves:<[10, South; 3, East; 7, South; &elipsis;> but was: <[10, South; 3, East; 7, South; &elipsis;> + at test.notstone.RobotTest.testMoveALongWay() +FAILURES!!!</screen> + + <para> + We will have to step through the code to find the problem because the assertions have + been made <emphasis>after</emphasis> the call has finished. Even so, the robot is a relatively simple example. + Some of us tried to do this with financial mathematics and it's very painful when a calculation fails. + A test failure usually meant stepping carefully through the code with an open spreadsheet nearby to check + the values. + </para> + + <para> Second, to test this behaviour at all, we had to add some functionality to the real code, - to hold all the <classname>MoveRequest</classname>s since the last <function>goto()</function>. - We don't have any other immediate need for <function>getRecentMovesRequests()</function>. + to hold all the <classname>MoveRequest</classname>s since the last <methodname>goto()</methodname>. + We don't have any other immediate need for <methodname>getRecentMovesRequests()</methodname> and, + what's worse, we've made an implicit promise to other people who work with the Robot code that we + will store routes, thus shrinking (ever so slightly) our room to manouver. We can mark the method as + test infrastructure, but that's messy and does tend to get ignored when in the heat of development. + </para> + + <para> Third, although it's not the case here, test suites based on extracting history from objects tend to need lots of utilities for constructing and comparing collections of values. The need to write - external code to manipulate an object is often a warning that its class is incomplete and that some - behaviour should be moved from the utility to the class. + external code to manipulate an object is often a warning that its class is getting too busy and + that some behaviour should be moved from the utility to the class. </para> <para> - Is there a better way? Can we find a style that will give me better error reporting and put the - behaviour in the right place? + Is there a better way? Can we find a technique that's less intrusive, that doesn't require the Robot + to hang on to unnecessary values? Can we have more helpful testfailures—that will fail faster, + like we know we're supposed to? What would happen if we really believed in the object design + maxim <quote>Tell, don't ask</quote>? </para> </section> <!-- Simple Unit Tests --> <section> - <title>Factoring Out The Motor</title> + <title>Breaking the Robot apart</title> + + <para> + Let's stop and think for a moment. Our robot is actually doing two things when we ask it to move + through the warehouse; it has to choose a route from its current position to its destination, + <emphasis>and</emphasis> it has to move along the route it has chosen. Those activities are + separate, even if they might happen at the same time. If we break out these responsibilities into two + objects, we can also separate the testing that goes with them. We can test that the route planning + object creates a suitable route and that the object that moves the Robot follows that route correctly. + We'll start with the "Robot Moving Object", what would be a good name for such a component? How about + <emphasis>Motor</emphasis>? If we leave the route planning in the <classname>Robot</classname> object, + we can intercept the requests that the <classname>Robot</classname> makes to its + <classname>Motor</classname>, which means that we can see inside without holding on to the route data. + </para> <para> - Our robot is actually doing two things when we ask it to move through the warehouse. It has to choose a - route from its current position to where it has to go, and it has to move along the route it has chosen. - If we separate these responsibilities into two objects, we break out the tests that go with them: - that the route planning object creates a suitable route and that the robot moving object follows that - route correctly. What's a good name for a robot moving object? How about <emphasis>Motor</emphasis>? - If we leave the route planning in the <classname>Robot</classname> object, we can intercept the - requests between the <classname>Robot</classname> and its <classname>Motor</classname> to see what's - happening inside. We'll start by defining an interface for <classname>Motor</classname>. We know that - there must be some kind of request, which we'll call <function>move()</function>, that takes a - move request as a parameter. We don't yet know what's in that request, so we'll define an empty - <classname>MoveRequest</classname> interface as a placeholder to get us through the compiler. + We'll start by defining an interface for <classname>Motor</classname>. We know that + there must be some kind of request, which we'll call <methodname>move()</methodname>, that takes some + kind of move request as a parameter. We don't yet know what's in that request, so we'll define an empty + <classname>MoveRequest</classname> interface as a placeholder to get us through the compiler. In dynamic + libraries, such as Smalltalk and Python, we don't even need to do that. </para> <programlisting> @@ -141,22 +193,27 @@ void move(MoveRequest request); }</programlisting> + <comment>From here!</comment> -<para>Now I need to initialise a Robot with a Motor when I create it. -Because I want to intercept the interaction between the Robot and its Motor -I cannot let the Robot instantiate its own Motor; there would be no way to -then intercept the Robot's movement requests. That leads me to pass a -Motor instance to the Robot's constructor.</para> - -<para>I can now write my tests to create a Robot with an implementation -of the <classname>Motor</classname> interface, that watches what's -happening in the Robot, and complains as soon as something goes wrong. -In fact, I will do this right now, before I start thinking about writing a -real implementation of the Motor interface, so that I know my Robot -implementation still works despite the extensive refactorings I have -performed. The first test is now:</para> + <para> + Now I need to initialise a Robot with a Motor when I create it. + Because I want to intercept the interaction between the Robot and its Motor + I cannot let the Robot instantiate its own Motor; there would be no way to + then intercept the Robot's movement requests. That leads me to pass a + Motor instance to the Robot's constructor. + </para> + + <para> + I can now write my tests to create a Robot with an implementation + of the <classname>Motor</classname> interface, that watches what's + happening in the Robot, and complains as soon as something goes wrong. + In fact, I will do this right now, before I start thinking about writing a + real implementation of the Motor interface, so that I know my Robot + implementation still works despite the extensive refactorings I have + performed. The first test is now: + </para> -<programlisting>public void testGotoSamePlace() { + <programlisting>public void testGotoSamePlace() { final Position POSITION = new Position(0, 0); robot.setCurrentPosition(POSITION); @@ -338,4 +395,4 @@ better. We hope that you can benefit from these techniques as well and that this book helps you to do so. </para> </section> <!-- Conclusions --> -</chapter> +</chapter> \ No newline at end of file Index: random.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/random.xml,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- random.xml 9 Aug 2002 18:13:01 -0000 1.4 +++ random.xml 10 Aug 2002 23:43:41 -0000 1.5 @@ -1,28 +1,26 @@ <chapter status="draft"> -<title>Random Acts</title> + <title>Random Acts</title> -<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> + <chapterinfo> + <author>Nat Pryce</author> + <copyright><year>2002</year> <holder>Nat Pryce</holder></copyright> + </chapterinfo> -</section> + <section> + <title>Introduction</title> + + <comment>Expand this</comment> + + <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> + + <comment>How do we test randomness?</comment> + + </section> <section> <title>Come Rain...</title> Index: htmlbook.css =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.css,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- htmlbook.css 10 Aug 2002 20:56:50 -0000 1.6 +++ htmlbook.css 10 Aug 2002 23:43:41 -0000 1.7 @@ -12,4 +12,5 @@ .sidebar { border: double black 1px; font-size: 80%; padding: 4px; text-align: center; margin-left: 80%; } .screenshot { margin-left: 20%; } .caption { font-size: 80%; font-style: italic; } -.guibutton { font-weight: bold; } \ No newline at end of file +.guibutton { font-weight: bold; } +.comment { font-style: italic; color: red; } \ No newline at end of file Index: extra_entities.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/extra_entities.xml,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- extra_entities.xml 10 Aug 2002 18:10:08 -0000 1.4 +++ extra_entities.xml 10 Aug 2002 23:43:41 -0000 1.5 @@ -1,3 +1,5 @@ <!ENTITY redbar SYSTEM "file:@docpath@/red_bar.xml"> <!ENTITY greenbar SYSTEM "file:@docpath@/green_bar.xml"> -<!ENTITY elipsis "<emphasis>[...]</emphasis>" > \ No newline at end of file + +<!ENTITY elipsis "<emphasis>[...]</emphasis>" > +<!ENTITY todo "<comment>To Do</comment>" > |
From: Steve F. <sm...@us...> - 2002-08-10 20:56:54
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv31674/doc/xdocs Modified Files: htmlbook.xsl how_mocks_happened.xml htmlbook.css Log Message: mostly formatting and layout Index: htmlbook.xsl =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.xsl,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- htmlbook.xsl 9 Aug 2002 23:48:20 -0000 1.6 +++ htmlbook.xsl 10 Aug 2002 20:56:50 -0000 1.7 @@ -6,8 +6,12 @@ <xsl:param name="use.extensions" select="'1'"/> <xsl:param name="html.stylesheet" select="'htmlbook.css'"/> + <xsl:param name="html.cleanup" select="1"/> <xsl:param name="callouts.extension" select="'1'"/> <xsl:param name="callout.defaultcolumn" select="'80'"/> + <xsl:param name="section.autolabel" select="0"/> + <xsl:param name="section.label.includes.component.label" select="0"/> + <xsl:param name="toc.section.depth">1</xsl:param> <xsl:template name="inline.gui"> <xsl:param name="content"> @@ -25,4 +29,10 @@ <xsl:call-template name="inline.gui"/> </xsl:template> + <xsl:param name="generate.toc"> + /book toc + /book/part toc + /article toc + chapter nop + </xsl:param> </xsl:stylesheet> Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- how_mocks_happened.xml 10 Aug 2002 12:22:26 -0000 1.2 +++ how_mocks_happened.xml 10 Aug 2002 20:56:50 -0000 1.3 @@ -33,7 +33,7 @@ <programlisting> public class TestRobot { - [...] + &elipsis; public void setUp() { robot = new Robot(); } @@ -156,7 +156,6 @@ implementation still works despite the extensive refactorings I have performed. The first test is now:</para> -<example><title>Testing using a mock motor</title> <programlisting>public void testGotoSamePlace() { final Position POSITION = new Position(0, 0); robot.setCurrentPosition(POSITION); @@ -172,7 +171,6 @@ assertEquals("Should be same", POSITION, robot.getPosition()); }</programlisting> -</example> <para>In this test, if there is a bug in the Robot code and the Motor gets requested to move, the mock implementation of @@ -183,23 +181,20 @@ <para>Now I know that my Robot class works I can write a real implementation of the Motor interface:</para> -<example><title>A real implementation of the Motor interface</title> <programlisting> public class OneSpeedMotor implements Motor { public void move(MoveRequest request) { turnBy(request.getTurn()); advance(request.getDistance()); } - [...] + &elipsis; } </programlisting> -</example> <para>As my tests grow, I can refactor the various mock implementations into a single, more sophisticated MockMotor and use it throughout all the Robot tests; for example:</para> -<example><title>Creating a mock motor class</title> <programlisting>public void MockMotor implements Motor { private ArrayList expectedRequests = new ArrayList(); public void move(MoveRequest request) { @@ -214,13 +209,11 @@ assertEquals("Too few requests", 0, this.expectedRequests.size()); } }</programlisting> -</example> <para>Which makes our tests look like:</para> -<example><title>Testing our robot with mock motors</title> <programlisting>public class TestRobot { - [...] + &elipsis; static final Position ORIGIN = new Position(0, 0); public void setUp() { mockMotor = new MockMotor(); @@ -255,7 +248,6 @@ } } </programlisting> -</example> </section> <!-- Factoring out the motor --> @@ -274,7 +266,6 @@ distance a Robot travels, I can do so without changing the implementation of either the robot or its motor:</para> -<example><title>Tracking distance with the decorator pattern</title> <programlisting> /** A decorator that accumulates distances, then passes the request * on to a real Motor. @@ -300,10 +291,9 @@ OneSpeedMotor realMotor = new OneSpeedMotor(); MotorTracker motorTracker = new MotorTracker(realMotor); Robot = new Robot(motorTracker); -// do some travelling here [...] +// do some travelling here &elipsis; println("Total distance was: " + motorTracker.getTotalDistance()); </programlisting> -</example> <para>Neither changing the Motor implementation nor adding tracking functionality would have been so easy if I had stuck with a testing Index: htmlbook.css =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.css,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- htmlbook.css 9 Aug 2002 23:48:41 -0000 1.5 +++ htmlbook.css 10 Aug 2002 20:56:50 -0000 1.6 @@ -1,14 +1,15 @@ /* htmlbook.css, a stylesheet for htmlbook */ BODY { font-family: Arial, Helvetica, sans-serif} -h1 { font-family: Arial, Helvetica, sans-serif; margin-top: 5%; } -h2 { font-family: Arial, Helvetica, sans-serif; margin-top: 3%;} -h3 { font-family: Arial, Helvetica, sans-serif; font-style: italic; font-weight: bold} +H1 { margin-top: 5%; } +H2 { margin-top: 3%;} + +DIV.section H2 { font-style: italic; font-weight: normal; } +DIV.section H3 { font-style: normal; font-size: small; } .programlisting { margin-left: 5%; } .screen { margin-left: 5%; } -.sidebar { border: double black 1px; font-size: 80%; padding: 4px; text-align: center; - margin-left: 80%; } +.sidebar { border: double black 1px; font-size: 80%; padding: 4px; text-align: center; margin-left: 80%; } .screenshot { margin-left: 20%; } .caption { font-size: 80%; font-style: italic; } .guibutton { font-weight: bold; } |
From: Steve F. <st...@m3...> - 2002-08-10 18:54:48
|
Some of you may be wondering why the website www.mockobjects.com is inaccessible. We've been changing registrar and appear to have falled through the cracks for the moment. Everything is still there on sourceforge. Normal service will be resumed just as soon as we can. Apologies Steve - - - - - - - - - - - - - - - Steve Freeman st...@m3... "Nonsense is just nonsense, but the study of nonsense is science." |
From: Steve F. <sm...@us...> - 2002-08-10 18:11:02
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv19276/doc/xdocs Modified Files: doc-book.xml Added Files: servlets_3.xml Log Message: ported servlets_3.xml --- NEW FILE: servlets_3.xml --- <chapter> <title>Test-driven servlets (iii)</title> <section> <title>Introduction</title> <para> So far, we've seen how to start writing a simple servlet from scratch and how to extend it incrementally. The implementation code is pretty clean now, but we still have two sets (plain and CSV) of very similar tests which suggests that we haven't pushed the refactoring as far as we could. To explain what I mean, take another look at one of our tests. </para> <programlisting> public void testAddTwoIntegers() throws ServletException, IOException { MockHttpServletRequest mockRequest = createMockRequest("3", "5", "/add"); mockResponse.setExpectedContentType("text/plain"); calculatorServlet.doGet(mockRequest, mockResponse); assertPageContains("3 /add 5", "Response page calculation"); assertPageContains("Result is 8", "Response page result"); }</programlisting> <para> This checks several things at once: that the parameters are pulled from the request correctly, that the calculation produces the right result, and that the calculation and its result are displayed correctly. The subtraction test also checks the same things but with a different calculation. There's a lot of duplication here; for example, we have eight tests, each of which exercises the parameter extraction—which seems excessive to me. There's something to be said for leaving some repetition in test code (unlike production code) to make it more readable, but that should be repetition of test implementation not of test functionality. With out current tests, we would have to work quite hard to make sure we only break one test at a time if, say, we wanted to change the calculation logic. Test-Driven Development allows you to plan no further ahead than you can see, but you can only get away with it if you keep your codebase clean. We need to fix the duplication in the test suite. </para> </section> <!-- Introduction --> <section> <title>Testing the writers</title> <section> <title>The first test</title> <para> I haven't quite sorted out exactly what is duplicated, so let's start by adding tests to check the calculation writers directly. For now, we'll bundle the tests for both writers into a <classname>CalculationWriterTest</classname> class. We start by writing a test for plain output of a succesful calculation. First, we already know from the servlet how the writer will be called. </para> <programlisting> MockHttpServletResponse mockResponse = new MockHttpServletResponse(); PlainCalculationWriter plainWr = new PlainCalculationWriter(); plainWr.initialize(mockResponse); plainWr.binaryOp("3", "an op", "5"); plainWr.result(66);</programlisting> <para>Next, we expect that the writer will set the content type in the response.</para> <programlisting> mockResponse.setExpectedContentType("text/plain");</programlisting> <para> Finally, we want to check that the response has had its content type set and that the writer constructs the page contents correctly; we'll copy <function>assertPageContains()</function> from the servlet test class. </para> <programlisting> assertPageContains("3 an op 5", "Plain text calculation"); assertPageContains("Result is 66", "Plain text result");</programlisting> <para>Put together, the whole test looks like this.</para> <programlisting> public class CalculationWriterTest extends TestCase { private MockHttpServletResponse mockResponse = new MockHttpServletResponse(); private PlainCalculationWriter plainWr = new PlainCalculationWriter(); &elipsis; public void testWriteResultPlain() throws ServletException, IOException { mockResponse.setExpectedContentType("text/plain"); plainWr.initialize(mockResponse); plainWr.binaryOp("3", "an op", "5"); plainWr.result(66); assertPageContains("3 an op 5", "Plain result calculation"); assertPageContains("Result is 66", "Plain result"); } }</programlisting> &greenbar; <para> The test passes? Good. The test says that, given an HTTP response and some inputs, a <classname>PlainCalculationWriter</classname> will set the response content type and write out those inputs in the given format. </para> <para> Wait a minute, what's all this about <literal>"an op"</literal> for the operation and <literal>66</literal> for the result? That's not a proper calculation. Exactly! We're not testing the calculation, we're testing how a succesful calculation would be displayed. The calculation writer really doesn't care what the calculation is, it just needs some values of the correct type. I could use values from a real calculation, but I like using silly values just to emphasise the point to anyone reading the test. In case you missed it, let me make the point again: <emphasis>this test is not about performing a calculation, it's about turning a calculation into text</emphasis>. </para> </section> <section> <title>The other tests</title> <para>While you're digesting that nugget, we'll fill in the other tests.</para> <programlisting> public class CalculationWriterTest &elipsis; private CvsCalculationWriter cvsWr = new CvsCalculationWriter(); public void testWriteErrorPlain() throws ServletException, IOException { mockResponse.setExpectedContentType("text/plain"); plainWr.initialize(mockResponse); plainWr.binaryOp("3", "an op", "5"); plainWr.error("an error"); assertPageContains("3 an op 5", "Plain error calculation"); assertPageContains("Error: an error", "Plain error"); } public void testWriteResultCsv() throws ServletException, IOException { mockResponse.setExpectedContentType("text/csv"); csvWr.initialize(mockResponse); csvWr.binaryOp("3", "an op", "5"); csvWr.result(66); assertPageContains("3,an op,5,66", "Csv result"); } public void testWriteErrorCsv() throws ServletException, IOException { mockResponse.setExpectedContentType("text/csv"); csvWr.initialize(mockResponse); csvWr.binaryOp("3", "an op", "5"); csvWr.error("an error"); assertPageContains("3,an op,5,,an error", "Csv error"); }</programlisting> <para> That's interesting. We only need two tests for each kind of writer: one for a result and one for an error. These tests don't need to include all the other conditions in the servlet tests, such as not being an integer, because those decisions are taken outside the writer. If we could avoid testing the writers in the servlet tests, we would have less duplication in the tests. Maybe we're on to something. </para> </section> <section> <title>Managing tests</title> <para> Now we have two test classes, <classname>CalculationWriterTest</classname> and <classname>CalculationServletTest</classname>. At the time of writing, the standard graphical JUnit runners will only run one <classname>TestCase</classname> at a time, but we must run all the tests after each change to make sure we haven't broken anything. To avoid picking out each test class by hand, we can write a third test class, <classname>AllTests</classname>, to gather together the test cases. I usually end up writing an <classname>AllTests</classname> class for each package. In this case, it looks like: </para> <programlisting> public class AllTests extends TestCase { public AllTests(String name) { super(name); } public static Test suite() { TestSuite result = new TestSuite(); result.addTestSuite(CalculatorServletTest.class); result.addTestSuite(CalculationWriterTest.class); return result; } }</programlisting> <para> We add a line to the <function>suite()</function> method for each test class we want to include. The test runner will work through the test suite generated by <classname>AllTests</classname> and run every test in every test class that it can find. If we run <classname>AllTests</classname> now we should have twelve tests passing, eight for the servlet and four for the calculation writers. </para> </section> <section> <title>What have we learned?</title> <para> We've tested a writer in isolation by applying the same principle here as we did to testing the calculation servlet. The tests avoid setting up the target object's real environment by providing a mock implemention of everything it interacts with and then calling it directly. For the servlet, that means calling <function>doGet()</function> with a fake HTTP request and response. In this test, we call <function>initialize()</function> with a fake HTTP response, and call <function>binaryOp()</function>, <function>result()</function>, and <function>error()</function> with dummy values. </para> <para> What we're beginning to see is a separation of concerns in the unit tests. The new tests for the calculation writers do not depend on any other part of the application. That's why I make a point of using silly values in this sort of test, I want to be clear about exactly what I'm testing. As you build up a code base this way, you start to see advantages. When you suddenly discover that a helper class is useful elsewhere, it comes ready parcelled with its own set of unit tests which also give you a good description of how to talk to it. More interestingly, as we shall soon see, this kind of testing forces you to clarify the conceptual edges within your code. Done right, the flex points you put in your program for testing turn out to be the flex points you need as your program evolves. </para> </section> </section> <!-- Testing the writers --> <section> <title>Adding some indirection</title> <section> <title>Introduction</title> <para> Now we've tested the calculation writers, we don't need to test them via the servlet tests. In fact, our commitment to removing duplication says that we <emphasis>shouldn't</emphasis> test them via the servlet tests. If we remove the writer logic from the <classname>CalculatorServlet</classname>, its tests can concentrate on parameter extraction and the calculation itself. One way of making that happen would be to substitute a fake calculation writer that just returns canned responses, but our current implementation won't let us do that; the calculation writers are created directly in the servlet, so there's nowhere to make the substitution. We need to slip in a level of indirection to give us the flexibility we need. First we need to do some work on our type sytem. </para> </section> <section> <title>The CalculationResponse interface</title> <para>Take another look at the body of our servlet method.</para> <programlisting> protected void doGet( &elipsis; CalculationWriter calcWriter = createCalculationWriter(request); calcWriter.<emphasis>initialize</emphasis>(response); calcWriter.<emphasis>binaryOp</emphasis>(value1, operation, value2); try { calcWriter.<emphasis>result</emphasis>(binaryOp(operation, asInt(value1), asInt(value2))); } catch (IllegalArgumentException ex) { calcWriter.<emphasis>error</emphasis>(ex.getMessage()); } }</programlisting> <para> None of the method names of the <classname>CalculatorWriter</classname> has anything to do with writing or printing, they're all about reacting to the various stages of performing a calculation. Perhaps our class is misnamed? I don't think so, because our writer classes <emphasis>are</emphasis> about printing values. Instead, we can use a Java interface to make this distinction clear. The new interface describes what to do with a calculation that the servlet implements, so let's call it a <classname>CalculationResponse</classname>. </para> <programlisting> public class CalculatorServlet &elipsis; protected void doGet( &elipsis; <emphasis>CalculationResponse calcResponse = createCalculationResponse(request);</emphasis> <emphasis>calcResponse</emphasis>.initialize(response); <emphasis>calcResponse</emphasis>.binaryOp(value1, operation, value2); try { <emphasis>calcResponse</emphasis>.result(binaryOp(operation, asInt(value1), asInt(value2))); } catch (IllegalArgumentException ex) { <emphasis>calcResponse</emphasis>.error(ex.getMessage()); } } } public abstract class CalculationWriter <emphasis>implements CalculationResponse</emphasis> &elipsis; <emphasis>public interface CalculationResponse { void initialize(HttpServletResponse response) throws IOException; void binaryOp(String value1, String operation, String value2); void result(int result); void error(String message); }</emphasis></programlisting> <para> That didn't hurt <emphasis>very</emphasis> much and it's taught us something about our code. Now we have a protocol for the servlet to talk to other objects about the calculations it performs. </para> </section> <section> <title>A new factory</title> <para> The thing that stops us from substituting a dummy <classname>CalculationResponse</classname> is that that we don't have a good place to hook in a different implementation, so we'll introduce a factory class to give us the level of indirection we need. First we create an empty factory class and move the <function>createCalculationResponse()</function> method across to it. Now we can instantiate the new factory and call its <function>create()</function> method. </para> <programlisting> public class CalculatorServlet &elipsis; protected void doGet( &elipsis; CalculationResponse calcResponse = <emphasis>new CalculationResponseFactory().create(request);</emphasis> &elipsis; <emphasis>public class CalculationResponseFactory { public CalculationResponse create(HttpServletRequest request) { if ("csv".equalsIgnoreCase(request.getParameter("format"))) { return new CsvCalculationWriter(); } else { return new PlainCalculationWriter(); } } }</emphasis></programlisting> <para> Taking another look at our new class, I'm not really happy with calling it a factory. It doesn't read well and, one day, we might not be returning new instances each time. We just want to describe an object that will start the process of responding, so let's call it a <classname>Responder</classname>. Similarly, in English we "respond to" a request, so let's change the method name. </para> <programlisting> <emphasis>public class Responder { public CalculationResponse respondTo(HttpServletRequest request) {</emphasis> &elipsis; public class CalculatorServlet &elipsis; protected void doGet( &elipsis; CalculationResponse calcResponse = <emphasis>new Responder().respondTo(request);</emphasis> &elipsis;</programlisting> <para> Finally, we don't need to create a new instance of the factory every time so we make it a field of the servlet, initialised when the object is created. </para> <programlisting> public class CalculatorServlet &elipsis; <emphasis>private Responder responder = new Responder();</emphasis> protected void doGet( &elipsis; CalculationResponse calcResponse = <emphasis>responder</emphasis>.respondTo(request);</programlisting> </section> <section> <title>New tests</title> <para> Our new <classname>Responder</classname> class describes how to set up a response object depending on an HTTP request. As with the calculation writers, we can test this new object separately. If we do that we have a motivation for removing duplication by pullling it out of the servlet tests. The new tests for this implementation are pretty simple, we just have to prove that it returns the right kind of writer for a given format; and we do remember to add the new test class to <classname>AllTests</classname>. </para> <programlisting> public class ResponderTest extends TestCase { private Responder responder = new Responder(); public void testCsvTextRequested() { MockHttpServletRequest mockRequest = makeMockRequest("csv"); assertEquals("Should be csv writer", CsvCalculationWriter.class, responder.respondTo(mockRequest).getClass()); } public void testUnknownFormatRequested() { &elipsis; public void testNoFormatRequested() { &elipsis; private MockHttpServletRequest makeMockRequest(final String format) { return new MockHttpServletRequest() { public String getParameter(String key) { if ("format".equals(key)) return format; throw new AssertionFailedError("Incorrect parameter " + key); } }; } }</programlisting> </section> <section> <title>Replacing the Responder</title> <para> We need one more indirection, and then we'll be ready. We need to change the behaviour of the <classname>Responder</classname> so that it can return a fake <classname>CalculationResponse</classname>. Once again, I don't want to leave test implementations in production code, so I need to make another substitution, which means that I need another interface. <classname>Responder</classname> is an effective name, so let's change the name of the class to <classname>TextResponder</classname> (it's about responders that write text) and extract <classname>Responder</classname> as an interface. </para> <programlisting> <emphasis>public interface Responder { CalculationResponse respondTo(HttpServletRequest request); }</emphasis> public class TextResponder <emphasis>implements Responder</emphasis> { &elipsis; public class CalculatorServlet extends HttpServlet { private Responder responder = new <emphasis>TextResponder();</emphasis> &elipsis;</programlisting> </section> <section> <title>What have we learned?</title> <para> We've now hidden the implementation and creation of the <classname>CalculationResponse</classname> objects from the rest of the servlet. This means that the two components, servlet and calculation response, can be changed independantly of each other, all they have in common is a protocol, a convention for communicating, defined by the interfaces <classname>Responder</classname> and <classname>CalculationResponse</classname>. </para> <para> We're beginning to see a pattern for removing duplication from tests. If we're testing a class through a containing class, write more tests to exercise it directly; this makes the duplication clear. Then convert the communication with the class to interfaces; this allows us to substitute a stub implementation in the container. (Unfortunately, Java requires two interfaces, one for getting hold of an intstance and one to talk to it, but a good refactoring development environment makes this easy to do.) Now we can test the containing class against a fake implementation of the other class, which makes the two sets of tests independant of each other. I'll show you what I mean. </para> </section> </section> <!-- Adding some indirection --> <section> <title>Splitting out the tests</title> <section> <title>Don't test the formatting</title> <para>Let's take another look at our servlet addition test.</para> <programlisting> public void testAddTwoIntegers() throws ServletException, IOException { MockHttpServletRequest mockRequest = createMockRequest("3", "5", "/add"); mockResponse.setExpectedContentType("text/plain"); calculatorServlet.doGet(mockRequest, mockResponse); assertPageContains("3 /add 5", "Response page calculation"); assertPageContains("Result is 8", "Response page result"); }</programlisting> <para> We can fold in the changes we've made. First, we set up a fake calculation response, responder, and HTTP request. We tell the servlet to use the mock <classname>Responder</classname> when it needs to create a calculation response, the mock <classname>Responder</classname> to return the mock <classname>CalculationResponse</classname> when <classname>respondTo()</classname> is called, and we set up the HTTP request as if the user had typed in an addition. </para> <programlisting> public void testAddTwoIntegers() throws ServletException, IOException { <emphasis>MockCalculationResponse mockCalcResponse = new MockCalculationResponse(); MockResponder mockResponder = new MockResponder(mockCalcResponse); calculatorServlet.setResponder(mockResponder);</emphasis> MockHttpServletRequest mockRequest = createMockRequest("3", "5", "/add");</programlisting> <para> Then we tell the various mock objects what to expect will happen to them. We tell the <classname>Responder</classname> to expect to be called with a given HTTP request, and we tell the <classname>CalculationResponse</classname> to expect to be called with a given HTTP response, binary operation, and result. </para> <programlisting> <emphasis>mockResponder.setExpectedRequest(mockRequest); mockCalcResponse.setExpectedInitialize(mockResponse); mockCalcResponse.setExpectedBinaryOp("3", "/add", "5"); mockCalcResponse.setExpectedResult(8);</emphasis></programlisting> <para> Then we call the servlet with our mock HTTP request and response, and ask the mock responder and mock calculation response to verify that our expectations have been met—that they were called with the right input values. </para> <programlisting> calculatorServlet.doGet(mockRequest, mockResponse); <emphasis>mockResponder.verify(); mockCalcResponse.verify();</emphasis> }</programlisting> <para> This is straightforward. The mock responder has the responsibility to check that it's called with the incoming HTTP request, and the mock calculation response will check that it's called with the right calculation values. So where do we check the output page? We don't, at least not here. That's covered in the <classname>CalculationResponse</classname> tests, all we need to check now is that the servlet talks to a <classname>CalculationResponse</classname> correctly given the values in the HTTP request.. Let me make the point again: <emphasis>this test is not about turning a calculation into text, it's about performing a calculation.</emphasis> </para> </section> <section> <title>A Mock Calculation Response</title> <para> Now we know where we want to go, but we haven't yet implemented the mock responder and calculation response that will make the test work. Normally I would start with the <classname>MockResponder</classname> because it's the first class that we come across in the test but, for now, I'll fix it up so that it just returns whichever <classname>CalculationResponse</classname> we set up in its constructor. That will get us through the first part of the servlet. I'll concentrate on the <classname>MockCalculationResponse</classname> because it's a little more complex so it will lead us in some interesting directions, but the concept is exactly the same. Let's look again at our use of the <classname>MockCalculationResponse</classname> as it's passed from the test to the servlet. </para> <programlisting> <emphasis>CalculatorServletTest</emphasis> <emphasis>mockCalcResponse</emphasis>.setExpectedInitialize(mockResponse); <emphasis>mockCalcResponse</emphasis>.setExpectedBinaryOp("3", "/add", "5"); <emphasis>mockCalcResponse</emphasis>.setExpectedResult(8); <emphasis>CalculatorServlet</emphasis> <emphasis>calcResponse</emphasis>.initialize(response); <emphasis>calcResponse</emphasis>.binaryOp(value1, operation, value2); try { <emphasis>calcResponse</emphasis>.result(binaryOp(operation, asInt(value1), asInt(value2))); } catch (IllegalArgumentException ex) { <emphasis>calcResponse</emphasis>.error(ex.getMessage()); } <emphasis>CalculatorServletTest</emphasis> <emphasis>mockCalcResponse</emphasis>.verify();</programlisting> <para> First, we need to test that the right response object is passed through. We store the expected value and check it against the object that the servlet passes through. </para> <programlisting> public class MockCalculationResponse implements CalculationResponse { <emphasis>private HttpServletResponse expectedResponse;</emphasis> public void initialize(HttpServletResponse response) throws IOException { <emphasis>Assert.assertEquals("CalculationResponse.response", expectedResponse, response);</emphasis> } public void setExpectedInitialize(HttpServletResponse response) { <emphasis>expectedResponse = response;</emphasis> } &elipsis; }</programlisting> <para> This test does not yet ensure that we have actually called the <function>initialize()</function> method. We can't be certain of this until after we've finished with the servlet, so we add a check in the <function>verify()</function> method that the HTTP response was set. </para> <programlisting> public class MockCalculationResponse implements CalculationResponse { private HttpServletResponse expectedResponse; <emphasis>private HttpServletResponse actualResponse;</emphasis> public void initialize(HttpServletResponse response) throws IOException { Assert.assertEquals("CalculationResponse.response", expectedResponse, response); <emphasis>actualResponse = response;</emphasis> } public void verify() { <emphasis>Assert.assertNotNull("CalculationResponse.response", actualResponse);</emphasis> } &elipsis; }</programlisting> </section> <section> <title>Further expectations</title> <para> So far, we've checked converting the servlet inputs and displaying the request. Next, we need to check that the calculation generates the right result. The obvious solution is to do the same as the previous tests with an <token>int</token>. We use an <classname>Integer</classname> object for the actual result, so that we can tell whether it's been set or not. </para> <programlisting> public class MockCalculationResponse implements CalculationResponse { <emphasis>private int expectedResult; private Integer actualResult;</emphasis> &elipsis; <emphasis>public void result(int result) { Assert.assertEquals("CalculationResponse.result", expectedResult, result); actualResult = new Integer(result); } public void setExpectedResult(int result) { expectedResult = result; }</emphasis> public void verify() { &elipsis; <emphasis>Assert.assertNotNull("CalculationResponse.result", actualResult);</emphasis> } }</programlisting> <para> Unfortunately, this makes the tests for bad input fail because they set an error rather than a result. </para> <screen> Testcase: testUnknownOperation took 0 sec FAILED junit.framework.AssertionFailedError: CalculationResponse.result at tdd.MockCalculationResponse.verify(MockCalculationResponse.java:66) at tdd.CalculatorServletTest.testUnknownOperation(CalculatorServletTest.java:66)</screen> &redbar; <para> If we change <varname>expectedResult</varname> to be an <classname>Integer</classname> object, we can tell when no expectation has been set because it will be null. If we also change <function>verify()</function> to use <function>assertEquals()</function>, it will pass when we need it to: when no expectation has been set and <function>result()</function> has not been called, both values will be <function>null</function>. I know that this is a repetition of the test in <function>result()</function>, but it'll do for now and I have a solution ready in the next chapter. Here's the code: </para> <programlisting> public class MockCalculationResponse implements CalculationResponse { private <emphasis>Integer</emphasis> expectedResult; private Integer actualResult; &elipsis; public void result(int result) { <emphasis>actualResult = new Integer(result);</emphasis> Assert.assertEquals("CalculationResponse.result", expectedResult, <emphasis>actualResult</emphasis>); } public void setExpectedResult(int result) { expectedResult = <emphasis>new Integer(result);</emphasis> } public void verify() { &elipsis; <emphasis>Assert.assertEquals("CalculationResponse.result", expectedResult, actualResult);</emphasis> } }</programlisting> &greenbar; <para>Now the tests pass, and we can do the same to check the error message.</para> </section> <section> <title>Fewer tests</title> <para> I expect you're wondering about the benefit from all this effort. Now we can halve the number of tests for the serlvet because we don't have to test the same behaviour with both text formats, plain and csv. We're relying on the calculation writer tests to check that side of things. All we're testing here is that, given the right parameters, we deliver the right values to a <classname>CalculationResponse</classname>. For example, an error test looks like: </para> <programlisting> public void testUnknownOperation() throws ServletException, IOException { mockRequest = createMockRequest("3", "5", "bad op"); mockResponder.setExpectedRequest(mockRequest); mockCalcResponse.setExpectedInitialize(mockResponse); mockCalcResponse.setExpectedBinaryOp("3", "bad op", "5"); mockCalcResponse.setExpectedError("unknown operation bad op"); calculatorServlet.doGet(mockRequest, mockResponse); mockResponder.verify(); mockCalcResponse.verify(); }</programlisting> <para> I could carry on with this, but the rest of the code is much the same, so I'll leave you to work it out for yourselves—or take a peek at the published examples. </para> </section> <section> <title>What have we learned</title> <para> The changes in this section have taken a while, but they're important because they show how to divide up code along its conceptual boundaries, <emphasis>and</emphasis> how to divide up the testing that goes with it. Now we can test a calculation separately from the way it's rendered on a page. This kind of rigour helps to keep your codebase nimble because it minimizes the ripple effects of making a change. We can change the the calculations or the way we print them independantly of each other. We don't have to write tests that exercise the calculation functionality over and over again, when what we really want to get at is the output formatting. </para> <para> In this section we're also beginning to see how we might use expectations. Now we have a <classname>MockCalculationResponse</classname> that can handle errors or results, and knows when to fail appropriately. What we're doing in practice is refactoring some of our assertions out of the tests into the test infrastructure; we're removing duplication. For example, every servlet test needs to check that the <classname>CalculationResponse</classname> is initialized with an <classname>HttpServletResponse</classname>. Now that assertion is implemented once in the <classname>MockCalculationResponse</classname> and called from each test. If we ever need to use a <classname>CalculationResponse</classname> elsewhere, we have the test infrastructure ready. </para> </section> </section> <!-- Splitting out the tests --> <section> <title>Summary</title> <para> This chapter is about just how far you can, or <emphasis>must</emphasis>, go when refactoring. It's not enough to refactor the code, you have to bring the tests with you as well. If you push the tests hard so that they really are independant of each other, they will force you to clarify the structure of your production code. In the servlet example, we now have a name, <classname>CalculationResponse</classname> for the interaction between a calculation and the component that presents it. When one of those changes, we'll know exactly where to slot in the new functionality. </para> <para> Strictly speaking, the code I've produced is more complicated than some test-driven developers would like. I've added a factory interface, <classname>Responder</classname>, so that I can substitute a different <classname>CalculationResponse</classname>. I'm prepared to live with this because, in production, it's just a matter of where to hang the response creation method. In return, I get to strip out the duplication from my tests. I'm not committing Test-Driven Heresy and designing ahead, because each change is still driven by a test, but I've found over time that the flex points I put in to make testing easier tend to be the flex points I need to add new functionality. </para> <para> There's another, more subtle point. When I write new code this way, I find that my classes are more focussed, with clean interfaces between them. Each class tends to end up doing just one thing well—like the <classname>CalculationWriter</classname>s that take in calculation details and send out text—so they're less dependant on external state. This makes the codebase easier to work with when I'm trying to avoid duplication; I'm more likely to be able to get at existing functionality to reuse it than in most conventionally written code that I've seen. </para> <para> You might well be thinking that it takes far too much effort to write this kind of test, particularly when starting the initial mock implementations. Partly this is a problem with Java's type system (enthusiasts for exotic languages may pause here to remember their favourite), but I've gone the long way around in this chapter because I wanted to show, from first principles, where these techniques come from. I'll make it easier in the next chapter. </para> </section> <!-- Summary --> </chapter> Index: doc-book.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/doc-book.xml,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- doc-book.xml 10 Aug 2002 12:22:59 -0000 1.7 +++ doc-book.xml 10 Aug 2002 18:11:00 -0000 1.8 @@ -8,6 +8,7 @@ <!ENTITY chp_servlets_1 SYSTEM "file://@docpath@/servlets_1.xml"> <!ENTITY chp_servlets_2 SYSTEM "file://@docpath@/servlets_2.xml"> + <!ENTITY chp_servlets_3 SYSTEM "file://@docpath@/servlets_3.xml"> <!ENTITY chp_jdbc_testfirst SYSTEM "file://@docpath@/jdbc_testfirst.xml"> @@ -45,6 +46,7 @@ &chp_servlets_1; &chp_servlets_2; + &chp_servlets_3; &chp_jdbc_testfirst; </part> |
From: Steve F. <sm...@us...> - 2002-08-10 18:10:12
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv19060/doc/xdocs Modified Files: extra_entities.xml Log Message: added elipsis entity Index: extra_entities.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/extra_entities.xml,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- extra_entities.xml 9 Aug 2002 17:55:48 -0000 1.3 +++ extra_entities.xml 10 Aug 2002 18:10:08 -0000 1.4 @@ -1,2 +1,3 @@ <!ENTITY redbar SYSTEM "file:@docpath@/red_bar.xml"> <!ENTITY greenbar SYSTEM "file:@docpath@/green_bar.xml"> +<!ENTITY elipsis "<emphasis>[...]</emphasis>" > \ No newline at end of file |
From: Steve F. <sm...@us...> - 2002-08-10 12:23:02
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv10470/doc/xdocs Modified Files: doc-book.xml Added Files: servlets_2.xml servlets_1.xml Log Message: ported servets 1 & 2 chapters --- NEW FILE: servlets_2.xml --- <chapter> <title>Test-driven servlets (ii)</title> <section> <title>Introduction</title> <para> In the previous chapter, I showed how to develop a simple servlet from tests. Our customers were so happy with the initial release that they approved another development cycle. This time, they want to improve the presentation of the results. After some discussion, we decide that the most important tasks to implement are: </para> <procedure> <step><para> display the calculation details in the ouptut, as well as the result </para></step> <step><para> if the request includes a <literal>format=csv</literal> parameter, then write the output as comma separated values and change the content type. Otherwise, write the output as before. </para></step> </procedure> <para> The new requirements suggest that we may have to factor out the part of the application that displays the results, but let's not commit ourselves just yet. Instead, we can implement the first task as simply as possible, and then see where the code takes us. </para> </section> <!-- Introduction --> <section> <title>Display the calculation details</title> <section> <title>The first test</title> <para> Adding the calculation details to the page should be straightforward. We can adapt one of our tests to something like: </para> <programlisting> public class CalculatorServletTest <emphasis>[...]</emphasis> public void testSubtractTwoIntegers() { MockHttpServletRequest mockRequest = createMockRequest("3", "5", "/add"); mockResponse.setExpectedContentType("text/plain"); calculatorServlet.doGet(mockRequest, mockResponse); <emphasis>checkPageContents("3 /add 5", "Response page calculation");</emphasis> checkPageContents("Result is 8", "Response page result"); }</programlisting> <para> Unfortunately, our current implementation of <function>checkPageContents()</function> matches the generated page exactly, so this test won't work because we have to verify two lines of output. We could write something that would extract and compare each line of the page, but I think we can do something simpler. If we change <function>checkPageContents()</function> to check whether the expected string is <emphasis>included</emphasis> in the output page, then this test becomes easy. Of course, this means that we would be giving up some precision because we wouldn't know where in the page each fragment occurs; for example, we cannot assert that the calculation details come before the result in the page. I'm happy with this because, for now, I just want to know that the servlet calculates the right values. We'll change <function>checkPageContents()</function> as follows and, because its behaviour has changed, rename it as well. </para> <programlisting> private void assertPageContains(String expected, String message) { String actualPage = mockResponse.getWrittenPage(); if (actualPage.indexOf(expected) == -1) { fail(message + " expected <" + actualPage + "> to include <" + expected + ">"); } }</programlisting> <para>Now we have a breaking test, so we know what to do.</para> <programlisting> Testcase: testAddTwoIntegers took 0 sec FAILED Response page calculation expected <Result is 8 > to include <3 /add 5> at tdd.CalculatorServletTest.assertPageContains(CalculatorServletTest.java:57) at tdd.CalculatorServletTest.testAddTwoIntegers(CalculatorServletTest.java:23)</programlisting> &redbar; <para>The fix is simple, we add another print line to <function>doGet()</function></para> <programlisting> response.setContentType("text/plain"); PrintWriter wr = response.getWriter(); try { int value1 = Integer.parseInt(request.getParameter("value1")); int value2 = Integer.parseInt(request.getParameter("value2")); String operation = request.getPathInfo(); <emphasis>wr.println(value1 + " " + operation + " " + value2);</emphasis> wr.println("Result is " + binaryOp(operation, value1, value2)); } catch (IllegalArgumentException ex) { wr.println("Error: " + ex.getMessage()); }</programlisting> <para> whicb passes the test. This looks easy, so let's get on and fix the other tests. <function>testSubtractTwoIntegers()</function> and <function>testBadOperation()</function> work with the same implementation. Next we try <function>testValueNotAnInteger()</function>: </para> <programlisting> Testcase: testValueNotAnInteger took 0 sec FAILED Response page calculation expected <Error: not an int > to include <3 /add not an int> at tdd.CalculatorServletTest.assertPageContains(CalculatorServletTest.java:60) at tdd.CalculatorServletTest.testValueNotAnInteger(CalculatorServletTest.java:53)</programlisting> &redbar; <para> Oops! When parsing an integer fails, the exception bypasses the line that prints the calculation details. I'm glad we had a test for what seemed like an obvious fix. We should extract the parameters as strings, to print the request, and <emphasis>then</emphasis> convert them to integers. </para> <programlisting> response.setContentType("text/plain"); PrintWriter wr = response.getWriter(); try { <emphasis>String value1Param = request.getParameter("value1"); String value2Param = request.getParameter("value2");</emphasis> String operation = request.getPathInfo(); wr.println(value1Param + " " + operation + " " + value2Param); <emphasis>int value1 = Integer.parseInt(value1Param); int value2 = Integer.parseInt(value2Param);</emphasis> wr.println("Result is " + binaryOp(operation, value1, value2)); } catch (IllegalArgumentException ex) { wr.println("Error: " + ex.getMessage()); }</programlisting> &greenbar; <para> Now it passes. We could just ship it, but the deal that allows us to do simple things to get through the test also requires us to clean up the mess before we go. Time to pay up. </para> </section> <section> <title>Interlude: Refactoring</title> <para> I'll show these two refactorings together but, trust me, I really did implement them one at a time and, of course, I ran the tests after every change. First, we notice that the code for getting values from the request won't throw an <classname>IllegalArgumentException</classname>, so we move this code out of the <token>try</token> block. If we don't, one day some other programmer (probably us when we've forgotten) will have to spend time trying to figure out whether we knew about some obscure failure condition or we just made a mistake. </para> <para> Next, the code for converting to integers is a bit repetitive. In nicer languages, we would just extend the <classname>String</classname> class, but here let's keep it simple and write a little helper method <function>asInt()</function>. With some variable renaming to improve readibility, we now have: </para> <programlisting> protected void doGet<emphasis>[...]</emphasis> <emphasis>String value1 = request.getParameter("value1"); String value2 = request.getParameter("value2"); String operation = request.getPathInfo();</emphasis> response.setContentType("text/plain"); PrintWriter wr = response.getWriter(); <emphasis>wr.println(value1 + " " + operation + " " + value2);</emphasis> try { wr.println("Result is " + binaryOp(operation, <emphasis>asInt(value1)</emphasis>, <emphasis>asInt(value2)</emphasis>)); } catch (IllegalArgumentException ex) { wr.println("Error: " + ex.getMessage()); } } <emphasis>private int asInt(String valueString) { return Integer.parseInt(valueString); }</emphasis></programlisting> </section> <section> <title>What have we learned?</title> <para> Even simple changes (or, perhaps, <emphasis>especially</emphasis> simple changes) can create flaws in our logic. If we keep them up to date, the unit tests will save us from our mistakes. We also have to think carefully about what we're testing. The tests for the generated page would have become messier once we'd added a second line. We made the tradeoff that checking that a string is <emphasis>included</emphasis> in the page, rather than an exact match, gives us enough confidence that the code still works and makes the tests much easier to write. Finally, we musn't forget that we have a debt of honour when developing test-first to keep the code clean and tidy before we move on. The final version expresses clearly what we expect the output to be: the details of the request, and either a result or an error. It's harder to see this in the unrestructured version. </para> </section> </section> <!-- Display the calculation details --> <section> <title>Comma separated values</title> <section> <title>The first test</title> <para> Now we have to add another format for the output page if the end user sets the <varname>format</varname> parameter. We consult our customers and they decide that they want the calculation description and result on the same line. We add a new test that sets the parameter and checks the new output. </para> <programlisting> public void testAddTwoIntegersCsv() throws ServletException, IOException { MockHttpServletRequest mockRequest = createMockRequest("3", "5", "/add", <emphasis>"csv"</emphasis>); mockResponse.setExpectedContentType("text/<emphasis>csv</emphasis>"); calculatorServlet.doGet(mockRequest, mockResponse); assertPageContains(<emphasis>"3,/add,5,8"</emphasis>, "Response page csv addition"); }</programlisting> <para> To make this work, we've extended <function>createMockRequest()</function> to add a format parameter and overloaded the original version to call the new one with a null value, so we don't have to change out existing tests. The implementation of the <classname>MockHttpServletRequest</classname> is becoming a bit messy but we'll live with that for now. </para> <programlisting> private MockHttpServletRequest <emphasis>createMockRequest</emphasis>(String value1, String value2, String pathInfo) { return createMockRequest(value1, value2, pathInfo, <emphasis>null</emphasis>); } private MockHttpServletRequest createMockRequest(final String value1, final String value2, final String pathInfo, <emphasis>final String format</emphasis>) { return new MockHttpServletRequest() { public String getParameter(String key) { <emphasis>if ("format".equals(key)) return format;</emphasis> <emphasis>[...]</emphasis> } } }</programlisting> <para>Of course the test fails. It tells us to fix the content type.</para> <programlisting> Testcase: testAddTwoIntegersCsv took 0 sec FAILED Content type expected:<text/csv> but was:<text/plain> at tdd.MockHttpServletResponse.setContentType(MockHttpServletResponse.java:21) at tdd.CalculatorServlet.doGet(CalculatorServlet.java:15)</programlisting> &redbar; <para>We look for the request parameter and change the content type if we need to.</para> <programlisting> protected void doGet( <emphasis>[...]</emphasis> <emphasis>if ("csv".equalsIgnoreCase(request.getParameter("format"))) { response.setContentType("text/csv"); } else {</emphasis> response.setContentType("text/plain"); <emphasis>}</emphasis> <emphasis>[...]</emphasis></programlisting> <para>We run the tests again and now the output string is wrong.</para> <programlisting> Testcase: testAddTwoIntegersCsv took 0 sec FAILED Response page calculation expected <3 /add 5 Result is 8 > to include <3,/add,5,8> at tdd.CalculatorServletTest.assertPageContains(CalculatorServletTest.java:69) at tdd.CalculatorServletTest.testAddTwoIntegersCsv(CalculatorServletTest.java:63)</programlisting> &redbar; <para> I have a small dilemma here. I need to branch both the content type and the content based on the format parameter. Should I hang on the decision and write multiple <token>if</token> statements, or have just one <token>if</token> statement but duplicate the code that both branches use? Surely this is an important design decision but I can't think of an obvious advantage for either of the choices, so I reach into my toolbox for my Executive Decision Support System. It comes up Heads, so we're going with the first option. </para> <programlisting> protected void doGet( <emphasis>[...]</emphasis> <emphasis>boolean isCsvFormat = "csv".equalsIgnoreCase(request.getParameter("format"));</emphasis> if (<emphasis>isCsvFormat</emphasis>) { response.setContentType("text/csv"); } else { response.setContentType("text/plain"); } PrintWriter wr = response.getWriter(); <emphasis>if (isCsvFormat) { wr.println(value1 + "," + operation + "," + value2 + "," + binaryOp(operation, asInt(value1), asInt(value2))); } else {</emphasis> wr.println(value1 + " " + operation + " " + value2); try { wr.println("Result is " + binaryOp(operation, asInt(value1), asInt(value2))); } catch (IllegalArgumentException ex) { wr.println("Error: " + ex.getMessage()); } <emphasis>}</emphasis> }</programlisting> &greenbar; </section> <section> <title>Handling errors</title> <para> We had a nasty surprise last time when tested for a non-integer value, so let's do that one next. Incidentally, our customers have asked us not to put results and errors in the same column, so there's an extra comma for the missing result value. </para> <programlisting> public void <emphasis>testValueNotAnIntegerCsv</emphasis>() throws ServletException, IOException { MockHttpServletRequest mockRequest = createMockRequest("3", "not an int", "/add", "csv"); mockResponse.setExpectedContentType("text/csv"); calculatorServlet.doGet(mockRequest, mockResponse); assertPageContains(<emphasis>"3,/add,not an int,,not an int"</emphasis>, "Not an integer error, CSV"); }</programlisting> <para> We need to shuffle our output statements to be included in the exception logic. It's a bit messy, but it makes the tests pass. </para> <programlisting> protected void doGet( <emphasis>[...]</emphasis> if (isCsvFormat) { <emphasis>wr.print(value1 + "," + operation + "," + value2 + ",");</emphasis> } else { wr.println(value1 + " " + operation + " " + value2); } try { <emphasis>if (isCsvFormat) { wr.println(binaryOp(operation, asInt(value1), asInt(value2))); } else {</emphasis> wr.println("Result is " + binaryOp(operation, asInt(value1), asInt(value2))); <emphasis>}</emphasis> } catch (IllegalArgumentException ex) { <emphasis>if (isCsvFormat) { wr.println("," + ex.getMessage()); } else {</emphasis> wr.println("Error: " + ex.getMessage()); <emphasis>}</emphasis> } }</programlisting> &greenbar; <para> This implementation also works for CSV version of the <emphasis>subtract</emphasis> and <emphasis>unknown operation</emphasis> tests. </para> </section> <section> <title>What have we learned</title> <para> We've added a new area of functionality without ever spending more than a couple of minutes without working tests. We've have accumulated enough infrastructure that it's becoming easy to add new tests and code. It's also pretty clear that there should be some good candidates for refactoring in all the repeated <token>if</token> statements. One of the skills in test-driven development is learning that sometimes you have to wait until the code is really clear about how it should be refactored. While working on these examples, I followed several false trails that weren't justified by the code because I was too keen to implement various clever abstractions. I still have an absolute commitment to releasing only clean, expressive code, but now I wait a little longer than I used to before starting larger refactorings. Finally, I really did mean that bit about flipping a coin; sometimes you just need to take a decision so you can write code and gain more experience. In Test Driven Development this is cheap because you're never far from a working system and with frequent releases it's easy to back off a weak choice. </para> </section> </section> <!-- Comma separated values --> <section> <title>Interlude: Refactoring</title> <para> I'm about to embark on a relatively large refactoring. There'll be a lot of code but hang on for the ride, because it'll take us in some interesting new directions. </para> <section> <title>The print statements</title> <para> I hope you find it screamingly obvious that we should do something about the multiple <token>if</token> statements. Let's start with the print statements, we'll pull each one out into a helper method. It's a minor change, but it helps us to clarify our ideas about what things should be called. </para> <programlisting> <emphasis>[...]</emphasis> if (isCsvFormat) { printBinaryOpCsv(wr, value1, operation, value2); } else { printBinaryOpPlain(wr, value1, operation, value2); } try { if (isCsvFormat) { printResultCsv(wr, binaryOp(operation, asInt(value1), asInt(value2))); } else { printResultPlain(wr, binaryOp(operation, asInt(value1), asInt(value2))); } } catch (IllegalArgumentException ex) { if (isCsvFormat) { printErrorCsv(wr, ex.getMessage()); } else { printErrorPlain(wr, ex.getMessage()); } }</programlisting> <para> It looks like there are two sets of very similar methods, but they're scattered across the code. Let's work on the structure by creating a couple of new classes, <classname>CsvCalculatorWriter</classname> and <classname>PlainCalculatorWriter</classname>, and attaching the relevant methods to each one. </para> <programlisting> public class CalculatorServlet <emphasis>[...]</emphasis> protected void doGet( <emphasis>[...]</emphasis> <emphasis>CsvCalculationWriter csvWriter = new CsvCalculationWriter(); PlainCalculationWriter plainWriter = new PlainCalculationWriter();</emphasis> if (isCsvFormat) { <emphasis>csvWriter.binaryOp(</emphasis>wr, value1, operation, value2); } else { <emphasis>plainWriter.binaryOp(</emphasis>wr, value1, operation, value2); } try { if (isCsvFormat) { <emphasis>csvWriter.result(</emphasis>wr, binaryOp(operation, asInt(value1), asInt(value2))); } else { <emphasis>plainWriter.result(</emphasis>wr, binaryOp(operation, asInt(value1), asInt(value2))); } } catch (IllegalArgumentException ex) { if (isCsvFormat) { <emphasis>csvWriter.error(</emphasis>wr, ex.getMessage()); } else { <emphasis>plainWriter.error(</emphasis>wr, ex.getMessage()); } } } <emphasis>public class CsvCalculationWriter { public void binaryOp(PrintWriter wr, String value1, String operation, String value2) { wr.print(value1 + "," + operation + "," + value2 + ","); } public void result(PrintWriter wr, int result) { wr.println(result); } public void error(PrintWriter wr, String message) { wr.println("," + message); } }</emphasis> <emphasis>[...]</emphasis></programlisting> <para> Clearly the two writer classes are nearly identical, so we can extract a common parent <classname>CalculationWriter</classname> </para> <programlisting> <emphasis>[...]</emphasis> CalculationWriter csvWriter = new CsvCalculationWriter(); CalculationWriter plainWriter = new PlainCalculationWriter(); <emphasis>[...]</emphasis> public class CsvCalculationWriter <emphasis>extends CalculationWriter</emphasis> { <emphasis>[...]</emphasis> public class PlainCalculationWriter <emphasis>extends CalculationWriter</emphasis> { <emphasis>[...]</emphasis> <emphasis>public abstract class CalculationWriter { public abstract void binaryOp(PrintWriter wr, String value1, String operation, String value2); public abstract void result(PrintWriter wr, int result); public abstract void error(PrintWriter wr, String message); }</emphasis></programlisting> <para> Now, finally, we can collapse the duplicated <token>if</token> statements and let polymorphism do the work. </para> <programlisting> public class CalculatorServlet <emphasis>[...]</emphasis> protected void doGet( <emphasis>[...]</emphasis> <emphasis>CalculationWriter calcWriter = createCalculationWriter(isCsvFormat);</emphasis> calcWriter.binaryOp(wr, value1, operation, value2); try { <emphasis>calcWriter</emphasis>.result(wr, binaryOp(operation, asInt(value1), asInt(value2))); } catch (IllegalArgumentException ex) { <emphasis>calcWriter</emphasis>.error(wr, ex.getMessage()); } } <emphasis>private CalculationWriter createCalculationWriter(boolean csvFormat) { if (csvFormat) { return new CsvCalculationWriter(); } else { return new PlainCalculationWriter(); } }</emphasis> }</programlisting> </section> <section> <title>Setting up the writer</title> <para> So now we're done? Not yet. We still have two <token>if</token> statements: one to set the content type and one to choose the calculation writer. We split them because we need to create the <classname>PrintWriter</classname> from the serlvet response between these two actions. If we move the management of the content type into our calculation writers, then we can let them set the content type. </para> <programlisting> public class CalculatorServlet <emphasis>[...]</emphasis> protected void doGet( <emphasis>[...]</emphasis> <emphasis>boolean isCsvFormat = "csv".equalsIgnoreCase(request.getParameter("format")); CalculationWriter calcWriter = createCalculationWriter(isCsvFormat); calcWriter.setContentType(response);</emphasis> PrintWriter wr = response.getWriter(); calcWriter.binaryOp(wr, value1, operation, value2); try { calcWriter.result(wr, binaryOp(operation, asInt(value1), asInt(value2))); } catch (IllegalArgumentException ex) { calcWriter.error(wr, ex.getMessage()); } } public class CsvCalculationWriter { <emphasis>public void setContentType(HttpServletResponse response) { response.setContentType("text/csv"); }</emphasis> <emphasis>[...]</emphasis> }</programlisting> <para> Looking at this version, the <varname>isCsvFormat</varname> variable is redundant, it's only used once. We can pass the request object to the <function>createCalculationWriter()</function> and make the decision there. This has the nice side-effect that the top-level <function>doGet()</function> no longer has to know anything about the output format. </para> <programlisting> <emphasis>[...]</emphasis> CalculationWriter calcWriter = createCalculationWriter(<emphasis>request</emphasis>); <emphasis>[...]</emphasis> private CalculationWriter createCalculationWriter(<emphasis>HttpServletRequest request</emphasis>) { if (<emphasis>"cvs".equalsIgnoreCase(request.getParameter("format"))</emphasis>) { return new CsvCalculationWriter(); } else { return new PlainCalculationWriter(); } }</programlisting> </section> <section> <title>Moving the print writer</title> <para> So <emphasis>now</emphasis> we're done? Well, we could leave it there, but I think there's more. Let's take another look at the body of the <function>doGet()</function> method. </para> <programlisting> CalculationWriter calcWriter = createCalculationWriter(request); calcWriter.setContentType(response); PrintWriter wr = response.getWriter(); calcWriter.binaryOp(wr, value1, operation, value2); try { calcWriter.result(wr, binaryOp(operation, asInt(value1), asInt(value2))); } catch (IllegalArgumentException ex) { calcWriter.error(wr, ex.getMessage()); }</programlisting> <para> Notice that we're passing exactly the same print writer to the calculation writer several times, and that the <varname>wr</varname> and <varname>calcWriter</varname> have about the same lifetime. The duplication suggests to me that the calculation writer should own the print writer. Let's make it an instance variable of <classname>CalculationWriter</classname>, as it's used by both the csv and plain versions, and remove the <varname>wr</varname> parameters. </para> <programlisting> public class CalculatorServlet <emphasis>[...]</emphasis> protected void doGet( <emphasis>[...]</emphasis> CalculationWriter calcWriter = createCalculationWriter(request); calcWriter.setContentType(response); <emphasis>calcWriter.setWriter(response.getWriter());</emphasis> calcWriter.binaryOp(<emphasis>value1, operation, value2</emphasis>); try { calcWriter.result(<emphasis>binaryOp(operation, asInt(value1), asInt(value2))</emphasis>); } catch (IllegalArgumentException ex) { calcWriter.error(<emphasis>ex.getMessage()</emphasis>); } } } public abstract class CalculationWriter { <emphasis>protected PrintWriter wr; public void setWriter(PrintWriter aWriter) { wr = aWriter; }</emphasis> public abstract void setContentType(HttpServletResponse response); public abstract void binaryOp(<emphasis>String value1, String operation, String value2</emphasis>); public abstract void result(<emphasis>int result</emphasis>); public abstract void error(<emphasis>String message</emphasis>); }</programlisting> <para> At this point, my Java code critic complains that the import of <classname>java.io.PrintWriter</classname> into <classname>CalculationWriter</classname> is unused. That means that the servlet code knows nothing about writing text. We seem to be drifting towards cleaner, more orthogonal code just by concentrating on removing local duplications. </para> </section> <section> <title>Setting up the response</title> <para> We're almost at the end of this bit, really we are. It's a subtle point, but we've got two methods, <function>setContentType()</function> and <function>setWriter()</function> on the calculation writer that (more or less) talk to the http response. Can we establish the link between the two objects just once? Let's push them into an initialisation method on the calculation writer. </para> <programlisting> public class CalculatorServlet <emphasis>[...]</emphasis> protected void doGet( <emphasis>[...]</emphasis> CalculationWriter calcWriter = createCalculationWriter(request); <emphasis>calcWriter.initialize(response);</emphasis> <emphasis>[...]</emphasis> } } public abstract class CalculationWriter { protected PrintWriter wr; <emphasis>public void initialize(HttpServletResponse response) throws IOException { setContentType(response); wr = response.getWriter(); }</emphasis> <emphasis>protected</emphasis> abstract void setContentType(HttpServletResponse response); public abstract void binaryOp(String value1, String operation, String value2); public abstract void result(int result); public abstract void error(String message); }</programlisting> <para> Notice that we've changed <function>setContentType()</function> from public to protected, as it's no longer called from outside the calculation writer. Incidentally, the new initialisation code has to be called before a <classname>CalculationWriter</classname> is usable, so it looks like it might belong in a constructor. I like the idea, but the code does have a side effect of setting state in the response object. It's an old habit, but I prefer not to change third party objects in constructors. Let's suspend that change until we can find a good reason to go one way or the other. </para> </section> <section> <title>What have we learned?</title> <para> By changing our code in a series of small, local improvements we've arrived at a clean implementation of the top-level servlet code and identified a new concept, the calculation writer. I'm presenting the final results, but I regularly find that such moves lead me in directions I hadn't expected. There are a couple of interesting lessons from code in this section. First, sometimes you have to create more code to get to less. Introducing the <classname>CsvCalculationWriter</classname> and <classname>PlainCalculationWriter</classname> classes took more lines of code, but also gave us a route towards removing duplication by showing us where we could hang a single decision point—the common <classname>CalculationWriter</classname> parent. I'm really pleased that setting of the response content type and creating its writer have ended up in the same method. We've made it hard to get this wrong, which is a typical bug when writing servlets. </para> <para> Second, it takes a while to get used to just how far you can push refactoring. We aren't taught this in college, and most development shops actively discourage changing working code. The deal with Test-Driven Development, however, is that you're allowed to get away with simple first implementations because you also promise to clean up afterwards. In return, you get early feedback about what the code is supposed to do and you're never far away from a working set of unit tests. </para> <para> One useful technique in this example is trying to remove local variables, especially when they're declared in the middle of a method. The lifetime of a local variable (from where it's created to where it's last used) is a clue that you can extract some kind of structure, such as a helper method or even a new object. If you can think of a name to describe the block of code, there's a concept there that deserves factoring out. </para> </section> </section> <!-- Interlude: Refactoring --> <section> <title>Summary</title> <para> This iteration has been mainly about changing the output and, step by step, it's led us to discover the <classname>CalculatorWriter</classname>. In the process, we've removed any knowledge of what a calculator writer actually does from the servlet method, so our code is getting more orthogonal without any dramatic effort on our part. Actually, it's probably the same amount of effort as if we'd sat down and figured it all out beforehand, but spread throughout the session in a series of local decisions. The design points in Test-First Development are in deciding what to put in a test and what to refactor next. The important advantage is that each decision is based on the experience of the problem that we've just gained from writing code, and only has to deal with the next immediate step. </para> <para> Many developers find it hard to give up "proper" design. It's what we're trained to do and it's a sign of seniority (Principal Architects are too valuable to write code). Personally, I think the code is too important to be left as a training exercise. As Fred Brooks points out when describing Chief Programmer Teams in <citetitle pubwork="book">The Mythical Man-Month</citetitle>, you would expect the critical part of your operation to be performed by the specialist surgeon you're paying extra for, not just under his or her direction. I also find the fine-grained incremental approach of Test-Driven Development much less stressful, because each decision is local and reversible. I rarely have to commit anything in stone. </para> <para> The other interesting issue is quality. Many developers are shocked by the degree of refactoring that Test-Driven Development requires, it really is a change from what most of us have been used to. Many development shops actively discourage changing code that works because the risks are too high (at one of my early jobs, we weren't even allowed to change the formatting!). Test-Driven Development says, first, that you <emphasis>can</emphasis> change the code because your tests will protect you from mistakes and, second, that you <emphasis>must</emphasis> change your code if you can improve it because that's how you maintain the agility you need to avoid designing ahead. If you don't believe me, you can try the experience I've repeatedly been through of taking over a conventionally written code base. If you extrapolate the additional difficulty of making changes to "normal" code from code that is well-factored and tested, you will understand how all those tests and "unnecessary" refactorings can slash your ongoing development costs. </para> </section> <!-- Summary --> </chapter> --- NEW FILE: servlets_1.xml --- <chapter> <title>Test-driven servlets (i)</title> <section> <title>Introduction</title> <para> Many software systems are event-driven: the application infrastructure converts an external event, such as a mouse click or a web query, to a software request which it passes to the application code. The application then responds to that request by, for example, serving up a web page or sending a message. The obvious way to test an event-driven application is to set up a copy of the system, send it events, and see what comes back. This the way to do integration testing, but is clumsy for unit testing. It's hard to get the fine-grained control over the state of the system to make sure you can exercise all the paths, and the test-and-code cycle can take too long to run—especially if you have to restart the server when the code changes. [...985 lines suppressed...] <para> So far, this example has been very simple. We have a couple of calculations with some basic presentation. On the other hand, we have found a mechanism for testing servlets without having to go through an application server and with tests that are (more or less) readable. </para> <para>In the next chapter, we will see what happens when the requirements become more complex.</para> </section> <!-- Summary --> <appendix> <title>A brief introduction to servlets</title> <para> If you're not familiar with Java servlets, here's a brief introduction. </para> </appendix> </chapter> Index: doc-book.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/doc-book.xml,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- doc-book.xml 10 Aug 2002 00:45:31 -0000 1.6 +++ doc-book.xml 10 Aug 2002 12:22:59 -0000 1.7 @@ -6,6 +6,9 @@ <!ENTITY part_introduction SYSTEM "file://@docpath@/introduction.xml"> <!ENTITY chp_how_mocks_happened SYSTEM "file://@docpath@/how_mocks_happened.xml"> + <!ENTITY chp_servlets_1 SYSTEM "file://@docpath@/servlets_1.xml"> + <!ENTITY chp_servlets_2 SYSTEM "file://@docpath@/servlets_2.xml"> + <!ENTITY chp_jdbc_testfirst SYSTEM "file://@docpath@/jdbc_testfirst.xml"> <!ENTITY part_testing_guis SYSTEM "file://@docpath@/testing_guis_1.xml"> @@ -40,9 +43,8 @@ <part> <title>Larger examples</title> - <chapter> - <title>Servlets</title> - </chapter> + &chp_servlets_1; + &chp_servlets_2; &chp_jdbc_testfirst; </part> @@ -51,24 +53,19 @@ <part> <title>Living with Unit Tests</title> - <chapter><title>Test organisation</title></chapter> - <chapter><title>Test smells</title></chapter> - <chapter><title>Retrofitting unit tests</title></chapter> </part> <part><title>Some other languages</title> - <chapter> - <title>C++</title> + <chapter><title>C++</title> <para>with Workshare?</para> </chapter> - <chapter><title>bash</title></chapter> - <chapter><title>Dynamic and metaprogramming</title></chapter> + <chapter><title>bash</title></chapter> </part> <part> |
From: Steve F. <sm...@us...> - 2002-08-10 12:22:30
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv10339/doc/xdocs Modified Files: how_mocks_happened.xml Log Message: corrected some tags Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- how_mocks_happened.xml 9 Aug 2002 23:49:40 -0000 1.1 +++ how_mocks_happened.xml 10 Aug 2002 12:22:26 -0000 1.2 @@ -50,7 +50,7 @@ This tells us that the robot thinks that it has arrived at the right place, but it doesn't tell us anything about how it got there; it might have trundled all the way round the warehouse before returning to its original location. We would like to know that the Robot hasn't moved. One option would - be to store and retrieve the route it took after each <methodname>goto()</methodname>. For example: + be to store and retrieve the route it took after each <function>goto()</function>. For example: </para> <programlisting> @@ -95,7 +95,7 @@ }</programlisting> <para> - where <methodname>makeExpectedLongWayMoves()</methodname> returns a list of the + where <function>makeExpectedLongWayMoves()</function> returns a list of the moves we expect the robot to make for this test. </para> @@ -105,8 +105,8 @@ access to the code while it is running. If one of these tests fail, we will have to step through the code to find the problem because the assertions have been made <emphasis>after</emphasis> the call has finished. Second, to test this behaviour at all, we had to add some functionality to the real code, - to hold all the <classname>MoveRequest</classname>s since the last <methodname>goto()</methodname>. - We don't have any other immediate need for <methodname>getRecentMovesRequests()</methodname>. + to hold all the <classname>MoveRequest</classname>s since the last <function>goto()</function>. + We don't have any other immediate need for <function>getRecentMovesRequests()</function>. Third, although it's not the case here, test suites based on extracting history from objects tend to need lots of utilities for constructing and comparing collections of values. The need to write external code to manipulate an object is often a warning that its class is incomplete and that some @@ -130,10 +130,10 @@ route correctly. What's a good name for a robot moving object? How about <emphasis>Motor</emphasis>? If we leave the route planning in the <classname>Robot</classname> object, we can intercept the requests between the <classname>Robot</classname> and its <classname>Motor</classname> to see what's - happening inside. We'll start by defining an interface for <interfacename>Motor</interfacename>. We know that - there must be some kind of request, which we'll call <methodname>move()</methodname>, that takes a + happening inside. We'll start by defining an interface for <classname>Motor</classname>. We know that + there must be some kind of request, which we'll call <function>move()</function>, that takes a move request as a parameter. We don't yet know what's in that request, so we'll define an empty - <interfacename>MoveRequest</interfacename> interface as a placeholder to get us through the compiler. + <classname>MoveRequest</classname> interface as a placeholder to get us through the compiler. </para> <programlisting> @@ -149,7 +149,7 @@ Motor instance to the Robot's constructor.</para> <para>I can now write my tests to create a Robot with an implementation -of the <interfacename>Motor</interfacename> interface, that watches what's +of the <classname>Motor</classname> interface, that watches what's happening in the Robot, and complains as soon as something goes wrong. In fact, I will do this right now, before I start thinking about writing a real implementation of the Motor interface, so that I know my Robot @@ -176,7 +176,7 @@ <para>In this test, if there is a bug in the Robot code and the Motor gets requested to move, the mock implementation of -<methodname>move()</methodname> +<function>move()</function> will fail immediately and stop the test; I no longer need to ask the Robot where it's been.</para> @@ -264,7 +264,7 @@ <para>My code moved in this direction because I was committed to unit testing but didn't want to record and expose unnecessary details about the state of my Robot -(the <methodname>getRecentMoveRequests()</methodname> method). +(the <function>getRecentMoveRequests()</function> method). As a result, I have better unit tests and a cleaner interface to the Robot class. But even better than that, by following the needs of my tests I have actually ended up improving the object model of the Robot by separating the |
From: Steve F. <sm...@us...> - 2002-08-10 00:45:34
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv30808/doc/xdocs Modified Files: doc-book.xml Added Files: jdbc_testfirst.xml Log Message: Ported jdbc chapter --- NEW FILE: jdbc_testfirst.xml --- <chapter> <title>Developing JDBC applications test-first</title> <section> <title>Introduction</title> <para> Many developers find unit testing software with third-party components, such as databases, hard to do. Usually the difficulties are because setting up the component to ensure no dependencies between unit tests is too complicated or slow. This paper uses JDBC, the Java database interface, to show how it is possible to avoid using a real database at all. It also shows how an approach based on Mock Objects can lead to more rigourous unit testing and, I believe, better structured code than working with a real database. </para> <para> There are two key skills for writing Mock Objects when developing code test-first. First, decide what a test would need to verify to show that it had passed, and add mock implementations of objects that represent those concepts. Second, do not attempt to reproduce real behaviour in a mock object; if they are becoming complex, revisit both the mock implementation and the code being tested to see if there is some intermediate concept to be factored out. </para> <para> To help structure the examples, let's imagine that our customers have commissioned us to write a mailing list system. Anyone can add and remove themselves from the list, but only the list owner can see the whole list and send messages to it. If we start by assuming that there's just one list, we have the following tasks: </para> <itemizedlist> <listitem><para> anyone can add their name and email address to the list; </para></listitem> <listitem><para> anyone who is a member of the list can remove themselves, using their email address; </para></listitem> <listitem><para> the owner of the list can see all the members of the list; </para></listitem> <listitem><para> the owner of the list can send a message to all the members. </para></listitem> </itemizedlist> </section> <!-- Introduction --> <section> <title>Add a member to the list</title> <para> We assume that access is controlled by the surrounding system and start to develop a <classname>MailingList</classname> class to manage the list. For a succesful insertion, we would expect certain things to happen: </para> <itemizedlist> <listitem><para> create a statement object with the appropriate insertion statement </para></listitem> <listitem><para> set the parameters for the insertion statement based on the member's email address and name </para></listitem> <listitem><para> execute the statement exactly once to make the insertion </para></listitem> <listitem><para> close the statement exactly once </para></listitem> </itemizedlist> <para>Let's write that into our first test.</para> <programlisting> public class TestMaililingList extends TestCaseMo { public void testAddNewMember() { connection.setExpectedPrepareStatementString(MailingList.INSERTION_SQL); statement.addExpectedSetParameters(new Object[] {EMAIL, NAME}); statement.setExpectedExecuteCalls(1); statement.setExpectedCloseCalls(1); } <emphasis>[...]</emphasis> }</programlisting> <para> This tells us that we will need to check assertions about the connection and statement objects. It also pushes us towards passing the connection object through to the mailing list object when we make the call to add a member. If we rename our database objects to make it explicit that they are mock implementations, the test becomes: </para> <programlisting> public class TestMaililingList extends TestCaseMo { public void testAddNewMember() throws SQLException { mockConnection.setExpectedPrepareStatementString(MailingList.INSERTION_SQL); mockStatement.addExpectedSetParameters(new Object[] {EMAIL, NAME}); mockStatement.setExpectedExecuteCalls(1); mockStatement.setExpectedCloseCalls(1); mailingList.addMember(mockConnection, EMAIL, NAME); mockStatement.verify(); mockConnection.verify(); } <emphasis>[...]</emphasis> }</programlisting> <para> Note that <function>testAddNewMember()</function>declares <classname>SQLException</classname> in its throws clause. This means that any <classname>SQLException</classname> will be caught by the JUnit framework and reported as an error (a breakdown in our environment). Strictly speaking, we should catch it and convert it to a failure (a mistake in coding), but this style is lightweight and accurate enough for many projects. </para> <para>We set the test up in another method:</para> <programlisting> public class TestMaililingList extends TestCaseMo { private MailingList mailingList = new MailingList(); private MockConnection mockConnection = new MockConnection(); private MockPreparedStatement mockStatement = new MockPreparedStatement(); public void setUp() { mockConnection.setupPreparedStatement(mockStatement); } <emphasis>[...]</emphasis> }</programlisting> <para> The simplest implementation to pass this first test is just to work through each of the expectations that we've set: </para> <programlisting> public class MailingList { public void addMember(Connection connection, String emailAddress, String name) throws SQLException { PreparedStatement statement = connection.prepareStatement(INSERT_SQL); statement.setString(1, emailAddress); statement.setString(2, name); statement.execute(); statement.close(); } }</programlisting> <para>You may have noticed that this implementation does not ensure that a statement will always be closed; we'll get to that later. </para> <section> <title>What have we learned?</title> <para> This first test is very localised. It tests exactly one thing, the successful addition of a list member, and has no external dependencies, such as the need for an installed database with its tables in the right state. In some situations, where infrastructure decisions have not been taken, this will allow a project to move ahead while the management are negotiating with your vendors. </para> <para> There are two aspects to testing database code: what we say to the database, and what the database expects to hear. This approach separates them, which makes it easier to pinpoint errors as the system grows. Of course, we will also need integration tests to check the whole process, but those should be more like an acceptance test for a larger module. </para> <para> This testing style pushes us towards passing objects around. In this case, we need access to the connection object to verify it, so the easiest thing is to pass it through to the mailing list along with the other parameters. In practice, many systems hold connections in pools to improve performance, so passing through a connection with each action is probably the right approach. We have found that this style of test-first programming pushes us towards the right design so often that we have come to rely on it. </para> </section> </section> <!-- add a member to the list --> <section> <title>Add an existing member to the list</title> <para> What happens if someone tries to add an email address twice? After further discussion, our customers decide that we should not accept the change and show an error message; we decide to throw a <classname>MailingListException</classname> when this happens. We can write a test to verify that such an exception is thrown if there is a duplicate record. </para> <programlisting> public void testAddExistingMember() throws SQLException { mockStatement.setupThrowExceptionOnExecute( new SQLException("MockStatment", "Duplicate", DatabaseConstants.UNIQUE_CONSTRAINT_VIOLATED)); mockConnection.setExpectedPrepareStatementString(MailingList.INSERTION_SQL); mockStatement.addExpectedSetParameters(new Object[] {EMAIL, NAME}); mockStatement.setExpectedExecuteCalls(1); mockStatement.setExpectedCloseCalls(1); try { mailingList.addMember(mockConnection, EMAIL, NAME); fail("should have thrown an exception"); } catch (MailingListException expected) { } mockStatement.verify(); mockConnection.verify(); }</programlisting> <para> The method <function>setupThrowExceptionOnExecute()</function> stores an exception that the mock SQL statement will throw when <function>>execute()</function> is called. We can force such events because our coding style allows us to pass a mock implementation through to the code that uses the statement. We still check that the right parameters are passed through to the connection and statement, and that <function>execute</function> and <function>close</function> are each called once. Our test ensures that this exception is caught and translated into a <classname>MailingListException</classname>. If this doesn't happen, then the code will step through from <function>addMember</function> to the <function>fail</function> call and throw an assertion error. Even with the extra complexity, the test is still precise and self-explanatory. </para> <para>To pass this test, we change the implementation to:</para> <programlisting> public class MailingList { public void addMember(Connection connection, String emailAddress, String name) throws MailingListException, SQLException { PreparedStatement statement = connection.prepareStatement(INSERT_SQL); <emphasis>try {</emphasis> statement.setString(1, emailAddress); statement.setString(2, name); statement.execute(); <emphasis>} catch (SQLException ex) { if (ex.getErrorCode() == DatabaseConstants.UNIQUE_CONSTRAINT_VIOLATED) { throw new MailingListException("Email address exists"); } else { throw ex; } } finally { statement.close(); }</emphasis> } }</programlisting> <para> We've added a <token>finally</token> block to make sure that the statement will always be closed, even if there are failures when using it. Interestingly, the code now also passes another test: </para> <programlisting> public void testPrepareStatementFailsForAdd() throws MailingListException, SQLException { <emphasis>mockConnection.setupThrowExceptionOnPrepare(new SQLException("MockConnection"));</emphasis> mockConnection.setExpectedPrepareStatementString(MailingList.INSERT_SQL); mockStatement.setExpectedExecuteCalls(0); mockStatement.setExpectedCloseCalls(0); try { list.addMember(mockConnection, EMAIL, NAME); fail("Should have thrown exception"); } catch (SQLException expected) { } mockConnection.verify(); mockStatement.verify(); }</programlisting> <para> which checks that we do not try to execute or close the statement should the connection fail, although we still need to confirm that we made the right <function>prepareStatement</function> request. </para> <section> <title>What have we learned?</title> <para> One of the hardest things to test is the throwing and management of exceptions. I have seen a lot of code that does not clean up resources on failure, or does so but is too complicated. Incremental, test-first programming is very effective at focussing a developer's attention on correct error handling by simulating exceptions that can be expensive, or even impossible, to generate from the real libary. Of course, how careful we should be depends on the application; we may not need precise cleanup for a single-action utility, but we certainly do for a long-lived server. </para> <para> The other design point to note from this example is that I have distinguished between mailing list exceptions (the system works, but was used incorrectly) and SQL exceptions (there was a system failure). As the application grows, we might want to translate those SQL exceptions into application-specific exceptions to help us manage exception handling. So far, however, that isn't necessary. </para> </section> </section><!-- Add an existing member to the list --> <section> <title>Remove a member from the list</title> <para> The next task is to remove someone from the list, based on their email address. The test for a successful removal is: </para> <programlisting> public void testRemoveMember() throws MailingListException, SQLException { mockStatement.setupUpdateCount(1); mockConnection.setExpectedPrepareStatementString(MailingList.DELETE_SQL); mockStatement.addExpectedSetParameters(new Object[] {EMAIL}); mockStatement.setExpectedExecuteCalls(1); mockStatement.setExpectedCloseCalls(1); list.removeMember(mockConnection, EMAIL); mockConnection.verify(); mockStatement.verify(); }</programlisting> <para> Once again, we check that the right parameters are passed in and the right methods called. A successful removal should return an update count greater than 0, so we use <function>setupUpdateCount()</function> to preload a return value in the statement object. </para> <para> We can also test removing someone who is not in the list by setting the update count to 0, which would imply that no rows match the email address in the deletion request. </para> <programlisting> public void testRemoveMissingMember() throws SQLException { mockStatement.setupUpdateCount(0); mockConnection.setExpectedPrepareStatementString(MailingList.DELETE_SQL); mockStatement.addExpectedSetParameters(new Object[] {EMAIL}); mockStatement.setExpectedExecuteCalls(1); mockStatement.setExpectedCloseCalls(1); try { list.removeMember(mockConnection, EMAIL); fail("Should have thrown exception"); } catch (MailingListException expected) { } mockConnection.verify(); mockStatement.verify(); }</programlisting> <para> An implementation of <function>removeMember()</function> to support both these tests is: </para> <programlisting> public void removeMember(Connection connection, String emailAddress) throws MailingListException, SQLException { PreparedStatement statement = connection.prepareStatement(DELETE_SQL); try { statement.setString(1, emailAddress); if (statement.executeUpdate() == 0) { throw new MailingListException("Could not find email address: " + emailAddress); } } finally { statement.close(); } }</programlisting> <section> <title>Refactoring the tests</title> <para> At this point we should start to notice repetition in the unit tests. In particular, each action on the member list requires a particular setup for the mock sql objects, and we always verify the same two objects. We refactor these into some helper methods: </para> <programlisting> private void setExpectationsForAddMember() { setExpectationsForPreparedStatement(MailingList.INSERT_SQL, new Object[] {EMAIL, NAME}); } private void setExpectationsForRemoveMember() { setExpectationsForPreparedStatement(MailingList.DELETE_SQL, new Object[] {EMAIL}); } private void setExpectationsForPreparedStatement(String sqlStatement, Object[] parameters) { mockConnection.setExpectedPrepareStatementString(sqlStatement); mockStatement.addExpectedSetParameters(parameters); mockStatement.setExpectedExecuteCalls(1); mockStatement.setExpectedCloseCalls(1); } private void verifyJDBC() { mockConnection.verify(); mockStatement.verify(); }</programlisting> <para> This allows us to simplify our existing tests and make them easier to read, for example: </para> <programlisting> public void testRemoveMember() throws MailingListException, SQLException { mockStatement.setupUpdateCount(1); setExpecationsForRemoveMember(); list.removeMember(mockConnection, EMAIL); verifyJDBC(); } public void testRemoveMissingMember() throws SQLException { mockStatement.setupUpdateCount(0); setExpectationsForRemoveMember(); try { list.removeMember(mockConnection, EMAIL); fail("Should have thrown exception"); } catch (MailingListException expected) { } verifyJDBC(); }</programlisting> </section> <section> <title>What have we learned?</title> <para> As the application grows, we find that the incremental costs of adding new tests falls as we start to benefit from the unit tests we have alread written. At first, the benefit is intellectual, we can write a new test by copying and adapting what we already have. The next stage is to refactor the tests to remove duplication. Maintaining and refactoring tests turns out to be as important as maintaining the code they exercise. The tests must be agile enough to follow the code as it is grows and changes. </para> <para> That said, we must maintain a balance here and not refactor the tests until they become too obscure to read; this applies particularly to verifications. In our example, the two verifiable objects are obvious enough that we can move them into a method with a meangingful name. Elsewhere it may be better to leave the calls to <function>verify()</function>in each test case to make it clear to the reader exactly what is being tested. </para> </section> </section> <!-- Remove a member from the list --> <section> <title>Show the members of the list</title> <para> Our next requirement is to show all the members of the list. The first question is how can we tell that we've seen everyone, so we create an object to work through all the list members; let's call it <classname>ListAction</classname> for now. We will set up a mock database and ask the <classname>ListAction</classname> object to verify that it has received the values that we expect it to when called by the <classname>MailingList</classname> object. </para> <section> <title>Extracting a single row</title> <para> The simplest first test is for a list with one member. We should ensure that the <classname>ListAction</classname> object receives the right member details, that the ResultSet is asked for the right column values and that <function>next()</function> is called twice. We also check the usual expectations about creating and closing a statement and executing the query. Our first test implements exactly this: </para> <programlisting> public void testListOneMember() throws SQLException { MockSingleRowResultSet mockResultSet = new MockSingleRowResultSet(); mockStatement.setupResultSet(mockResultSet); mockResultSet.addExpectedNamedValues( COLUMN_NAMES, new Object[] {EMAIL, NAME}); mockResultSet.setExpectedNextCalls(2); mockListAction.addExpectedMember(EMAIL, NAME); setExpectationsForListMembers(); list.applyToAllMembers(mockConnection, mockListAction); verifyJDBC(); mockResultSet.verify(); mockListAction.verify(); }</programlisting> <para> The value expectations in a <classname>MockSingleRowResultSet</classname> serve two purposes. They are checked to verify that the client has asked for exactly the right columns, either by name or position, and they store the values that are returned. It is arguable that these two functions should be split, but the pattern occurs often enough that we have implemented it in an <classname>ExpectationMap</classname>. The <classname>ListAction</classname> class maintains expectations about the name and email address of the members of the list. If we write enough code to fulfil just this test, we will have a sketch of the structure of the method: </para> <programlisting> public void applyToAllMembers(Connection connection, ListAction listAction) throws SQLException { Statement statement = connection.createStatement(); ResultSet results = statement.executeQuery(LIST_SQL); while (results.next()) { listAction.applyTo(results.getString("email_address"), results.getString("name")); } statement.close(); }</programlisting> </section> <section> <title>Extracting more than one member</title> <para> This first test proves that we can extract the right values from a valid row in a <classname>ResultSet</classname>. Once we have a test for this, our other tests can concentrate on other aspects, such as handling different numbers of valid rows. We do not need to re-test extraction in every case. For example, when testing for different numbers of list members we only need to set expectations for the number of times we call the <function>listAction</function> object, we do not need to check again that the right values have been received. This makes tests more precise and orthogonal, and so easier to understand when they fail. It also makes test cases and mock objects easier to write because each one does less. </para> <para> For example, the test for listing two members does not set any expectations for the list member name and email address. This version does set column names and some dummy values, but these are only necessary to avoid type casting errors when returning values from the <classname>ResultSet</classname>; they are also used to define the number of rows to return. The expectations we <emphasis>do</emphasis> set are concerned with the number of times various methods are called. </para> <programlisting> public void testListTwoMembers() throws SQLException { MockMultiRowResultSet mockResultSet = new MockMultiRowResultSet(); mockStatement.setupResultSet(mockResultSet); mockResultSet.setupColumnNames(COLUMN_NAMES); mockResultSet.setupRows(TWO_ROWS); mockResultSet.setExpectedNextCalls(3); mockListAction.setExpectedMemberCount(2); setExpectationsForListMembers(); list.applyToAllMembers(mockConnection, mockListAction); verifyJDBC(); mockResultSet.verify(); mockListAction.verify(); }</programlisting> <para> The test for a <classname>ResultSet</classname> with no rows is even simpler, we set up no row values at all and we tell the mock <classname>ListAction</classname> object to expect not to be called. At this point, we can also factor out setting up the mock <classname>ResultSet</classname> into a helper method. </para> <programlisting> public void testListNoMembers() throws SQLException { MockMultiRowResultSet mockResultSet = makeMultiRowResultSet(); mockResultSet.setExpectedNextCalls(1); mockListAction.setExpectNoMembers(); setExpectationsForListMembers(); list.applyToAllMembers(mockConnection, mockListAction); verifyJDBC(); mockResultSet.verify(); mockListAction.verify(); }</programlisting> </section> <section> <title>Handling failures</title> <para> Our simple implementation of <function>applyToAllMembers</function> can support all these tests, but we know that we still have to manage exceptions. We should write an additional test to confirm that we can cope if the <classname>ResultSet</classname> fails. As with the test for adding an existing member, we wrap the call to the <classname>ListAction</classname> object in a <token>try</token> block and fail if the exception is not thrown. We also use the same technique as before to tell the mock <classname>ResultSet</classname> to throw an exception when anyone tries to get a value. </para> <programlisting> public void testListResultSetFailure() { MockMultiRowResultSet mockResultSet = makeMultiRowResultSet(); mockResultSet.setupRows(TWO_ROWS); mockResultSet.setupThrowExceptionOnGet(new SQLException("Mock Exception")); mockResultSet.setExpectedNextCalls(1); mockListAction.setExpectNoMembers(); setExpectationsForListMembers(); try { list.applyToAllMembers(mockConnection, mockListMembers); fail("Should have thrown exception"); } catch (SQLException expected) { } mockResultSet.verify(); mockListMembers.verify(); }</programlisting> <para> One significant point about this test is that it clearly defines the behaviour of <function>applyToAllMembers</function> in the presence of failures. We do not, for example, try to carry on should a record fail. Developing test-first and publishing the tests gives users of the code a much clearer description of how to use it than most conventional documentation. </para> <para> Our implementation will not pass this test because it has no <token>finally</token> clause, so we add it now. </para> <programlisting> public void applyToAllMembers(Connection connection, ListAction listAction) throws SQLException { Statement statement = connection.createStatement(); try { ResultSet results = statement.executeQuery(LIST_SQL); while (results.next()) { listAction.applyTo(results.getString("email_address"), results.getString("name")); } } finally { statement.close(); } }</programlisting> <para> We might also add tests to check that we correctly handle failures when the connection cannot create a statement or the statement cannot execute the query. For a long-lived server, this sort of error handling avoids resource leakages that can be very difficult to find in production. </para> </section> <section> <title>Presenting the results</title> <para> Now we can ask a <classname>MailingList</classname> object to process all the members of the list by passing an object of type <classname>ListAction</classname>. We might present the results by implementing a <classname>ConvertToMemberNode</classname> class that adds each member's details to an XML document. That rendering is managed by another class, <classname>ListDocument</classname>, that is passed in when starting the action. </para> <programlisting> class ConvertToMemberNode implements ListAction { ListDocument listDocument; public ConvertToMemberNode(ListDocument aListDocument) { listDocument = aListDocument; } public void applyTo(String email, String name) { listDocument.startMember(); listDocument.addMemberEmail(email); listDocument.addMemberName(name); listDocumnet.endMember(); } }</programlisting> <para>Of course this new class should have its own set of unit tests. </para> </section> <section> <title>What have we learned?</title> <para> A common difficulty when testing networks of objects is the cost of setting up the test environment. These techniques show that we can manage this by being very precise about what each test verifies, and by providing just enough mock implementation to get us through. Teasing out the different aspects of what we are testing to make the tests more orthogonal has several advantages: the tests are more precise, so errors are easier to find; smaller tests are easier to implement and easier to read; and, the mock implementations can be more specialised and so simpler to implement. </para> <para> An alternative approach is to use one of the available stub JDBC implementations, or even a scratch copy of a real database. This is a good technique when getting started with unit testing or when retrofitting tests to an existing code base, but the mock object approach still has some advantages: </para> <itemizedlist> <listitem><para> the setup is cheaper, which makes the tests easier to maintain and quicker to run; </para></listitem> <listitem><para> failures can happen as soon as something unexpected happens, rather than verifying the result afterwards; </para></listitem> <listitem><para> we can verify behaviour such as the number of times times <function>close()</function> is called; and, </para></listitem> <listitem><para> we can force unusual conditions such as SQL exceptions. </para></listitem> </itemizedlist> </section> </section> <!-- Show the members of the list --> <section> <title>Send a message to all the members</title> <para> Our final requirement is to send a message to all the members of the list. We can do this by providing another implementation of the <classname>ListAction </classname> interface, in this case a <classname>AddMemberToMessage</classname> class. We have to make a design choice here about how to handle any messaging exceptions: we can either stop immediately, or collect the failures and process them at the end; for now, we shall stop processing immediately. The <classname>Message</classname> object is set up by the calling code, which then sends it to all the recipients. </para> <programlisting> class AddMembersToMessage implements ListAction { Message message; public AddMemberToMessage(Message aMessage) { message = aMessage; } public void applyTo(String email, String name) throws MailingListException { try { message.addRecipient(RecipientType.TO, new InternetAddress(email, name)); } catch (MessagingException ex) { throw new MailingListException("Adding member to message", email, name, ex); } } }</programlisting> <para> The <classname>MessagingException</classname> exception is checked, so we have to handle it here or propagate it through the code. We want to keep the <classname>ListAction </classname> interface generic, so we translate the <classname>MessagingException</classname> to a <classname>MailingListException</classname>, and propagate that instead. Now we have a new failure mode, so we write an additional unit test that ensures that we clean up properly after a <classname>MailingListException</classname>. </para> <programlisting> public void testListmembersFailure() throws SQLException { MockMultiRowResultSet mockResultSet = makeMultiRowResultSet(); mockResultSet.setupRows(TWO_ROWS); mockListAction.setupThrowExceptionOnMember(new MailingListException()); mockResultSet.setExpectedNextCalls(1); setExpectationsForListMembers(); try { list.applyToAllMembers(mockConnection, mockListAction); fail("Should have thrown exception"); } catch (MailingListException expected) { } mockResultSet.verify(); mockListAction.verify(); }</programlisting> <section> <title>Tell, don't ask</title> <para> This approach to test-first development has led to a solution that may appear needlessly complex, as against just returning a collection of members from the <classname>MailingList</classname>. Our experience, however, is that code written this way is much more flexible. For example, if we later decide to ignore message errors and keep sending, we can do so by collecting them in the <classname>AddMemberToMessage</classname> class and processing them after the message has been sent. Similarly, if our mailing list becomes very large, we can start to process recipients in small batches and do not have to worry about creating excessively large collections of list members. On the other hand, if we do want to return a list of members, we can still write another <classname>ListAction</classname> implementation that appends each member to a collection. </para> </section> </section> <!-- Send a message to all the members --> <section> <title>Summary</title> <para> This example shows how far you can you can isolate your unit tests from each other and avoid external dependencies by adopting Mock Objects. More interestingly, this approach to unit testing leads your code towards a style that is both flexible and easy to test, including testing system exceptions. As an application grows, common test infrastructure gets pushed into the mock implementations and the incremental cost of adding new tests drops substantially. In our experience, the first few tests with a complex new library, such as JDBC, can be slow to write but the mock implementations stabilise very quickly—particularly when using run-time proxies or an IDE to generate stub methods. </para> </section> <!-- Summary --> </chapter> Index: doc-book.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/doc-book.xml,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- doc-book.xml 9 Aug 2002 23:47:18 -0000 1.5 +++ doc-book.xml 10 Aug 2002 00:45:31 -0000 1.6 @@ -6,6 +6,8 @@ <!ENTITY part_introduction SYSTEM "file://@docpath@/introduction.xml"> <!ENTITY chp_how_mocks_happened SYSTEM "file://@docpath@/how_mocks_happened.xml"> + <!ENTITY chp_jdbc_testfirst SYSTEM "file://@docpath@/jdbc_testfirst.xml"> + <!ENTITY part_testing_guis SYSTEM "file://@docpath@/testing_guis_1.xml"> <!ENTITY chp_random SYSTEM "file://@docpath@/random.xml"> <!ENTITY notes SYSTEM "file://@docpath@/notes.xml"> @@ -42,9 +44,7 @@ <title>Servlets</title> </chapter> - <chapter> - <title>JDBC</title> - </chapter> + &chp_jdbc_testfirst; </part> &part_testing_guis; |
From: Steve F. <sm...@us...> - 2002-08-09 23:51:00
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv7595/doc/xdocs Removed Files: brief_introduction.xml Log Message: Renamed to how_mocks_happened.xml --- brief_introduction.xml DELETED --- |
From: Steve F. <sm...@us...> - 2002-08-09 23:49:42
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv6874/doc/xdocs Added Files: how_mocks_happened.xml Log Message: Renamed from Introduction --- NEW FILE: how_mocks_happened.xml --- <chapter> <title>How Mock Objects Happened</title> <section> <title>Introduction</title> <para> Mock Objects is a development technique that lets you unit test classes that you didn't think you could <emphasis>and</emphasis> helps you write better code whilst doing so. This chapter uses a simple example to show a compressed history of how Mock Objects was discovered by refactoring from conventional unit tests and why they're useful. </para> <para> Mock Objects is based on two key concepts: replace everything except the code under test with mock implementations that emulate the rest of the environement, and put the test assertions <emphasis>inside</emphasis> those mock implementations so that you can validate the interactions between the objects in your test case as they occur. </para> </section> <!-- Introduction --> <section> <title>Simple Unit Tests</title> <para> Let's dive straight into a worked example. We're writing software to direct robots through a warehouse. Given a starting point on a grid, it will find a route through the stacks to a specified destination. How can we test this? The standard approach is to write some unit tests that create a robot, call some methods on it, and then check that the resulting state is what we expect. Our tests will have to tell us, for example, that the robot arrives where it was directed to go and does not hit anything on the way. A easy test to start with is to check that a robot does not move if it is told to go to its current location: </para> <programlisting> public class TestRobot { [...] public void setUp() { robot = new Robot(); } public void testGotoSamePlace() { final Position POSITION = new Position(1, 1); robot.setCurrentPosition(POSITION); robot.goTo(POSITION); assertEquals("Should be same", POSITION, robot.getPosition()); } }</programlisting> <para> This tells us that the robot thinks that it has arrived at the right place, but it doesn't tell us anything about how it got there; it might have trundled all the way round the warehouse before returning to its original location. We would like to know that the Robot hasn't moved. One option would be to store and retrieve the route it took after each <methodname>goto()</methodname>. For example: </para> <programlisting> public void testGotoSamePlace() { final Position POSITION = new Position(0, 0); robot.setCurrentPosition(POSITION); robot.goTo(POSITION); assertEquals("Should be empty", 0, robot.getRecentMoveRequests().size()); assertEquals("Should be same", POSITION, robot.getPosition()); }</programlisting> <para>The test might be to move one point on the grid:</para> <programlisting> public void testMoveOnePoint() { final Position DESTINATION = new Position(1, 0); robot.setCurrentPosition(new Position(0, 0)); robot.goto(DESTINATION); assertEquals("Should be destination", DESTINATION, robot.getPosition()); List moves = robot.getRecentMoveRequests(); assertEquals("Should be one move", 1, moves.size()); assertEquals("Should be same move", new MoveRequest(1, MoveRequest.SOUTH), moves.get(1)); }</programlisting> <para> As the tests become more complex, we pull some of the detail out into helper methods to make the code more legible: </para> <programlisting> public void testMoveALongWay() { final Position DESTINATION = new Position(34, 71); robot.setCurrentPosition(new Position(0, 0)); robot.goto(DESTINATION); assertEquals("Should be destination", DESTINATION, robot.getPosition()); assertEquals("Should be same moves", makeExpectedLongWayMoves(), robot.getRecentMoves()); }</programlisting> <para> where <methodname>makeExpectedLongWayMoves()</methodname> returns a list of the moves we expect the robot to make for this test. </para> <para> There are problems with this approach to testing. First, tests like this are effectively small-scale integration tests, they set up pre-conditions and test post-conditions but they have no access to the code while it is running. If one of these tests fail, we will have to step through the code to find the problem because the assertions have been made <emphasis>after</emphasis> the call has finished. Second, to test this behaviour at all, we had to add some functionality to the real code, to hold all the <classname>MoveRequest</classname>s since the last <methodname>goto()</methodname>. We don't have any other immediate need for <methodname>getRecentMovesRequests()</methodname>. Third, although it's not the case here, test suites based on extracting history from objects tend to need lots of utilities for constructing and comparing collections of values. The need to write external code to manipulate an object is often a warning that its class is incomplete and that some behaviour should be moved from the utility to the class. </para> <para> Is there a better way? Can we find a style that will give me better error reporting and put the behaviour in the right place? </para> </section> <!-- Simple Unit Tests --> <section> <title>Factoring Out The Motor</title> <para> Our robot is actually doing two things when we ask it to move through the warehouse. It has to choose a route from its current position to where it has to go, and it has to move along the route it has chosen. If we separate these responsibilities into two objects, we break out the tests that go with them: that the route planning object creates a suitable route and that the robot moving object follows that route correctly. What's a good name for a robot moving object? How about <emphasis>Motor</emphasis>? If we leave the route planning in the <classname>Robot</classname> object, we can intercept the requests between the <classname>Robot</classname> and its <classname>Motor</classname> to see what's happening inside. We'll start by defining an interface for <interfacename>Motor</interfacename>. We know that there must be some kind of request, which we'll call <methodname>move()</methodname>, that takes a move request as a parameter. We don't yet know what's in that request, so we'll define an empty <interfacename>MoveRequest</interfacename> interface as a placeholder to get us through the compiler. </para> <programlisting> public interface Motor { void move(MoveRequest request); }</programlisting> <para>Now I need to initialise a Robot with a Motor when I create it. Because I want to intercept the interaction between the Robot and its Motor I cannot let the Robot instantiate its own Motor; there would be no way to then intercept the Robot's movement requests. That leads me to pass a Motor instance to the Robot's constructor.</para> <para>I can now write my tests to create a Robot with an implementation of the <interfacename>Motor</interfacename> interface, that watches what's happening in the Robot, and complains as soon as something goes wrong. In fact, I will do this right now, before I start thinking about writing a real implementation of the Motor interface, so that I know my Robot implementation still works despite the extensive refactorings I have performed. The first test is now:</para> <example><title>Testing using a mock motor</title> <programlisting>public void testGotoSamePlace() { final Position POSITION = new Position(0, 0); robot.setCurrentPosition(POSITION); Motor mockMotor = new Motor() { public void move(MoveRequest request) { fail("There should be no moves in this test"); } }; robot.setMotor(mockMotor); robot.goTo(POSITION); assertEquals("Should be same", POSITION, robot.getPosition()); }</programlisting> </example> <para>In this test, if there is a bug in the Robot code and the Motor gets requested to move, the mock implementation of <methodname>move()</methodname> will fail immediately and stop the test; I no longer need to ask the Robot where it's been.</para> <para>Now I know that my Robot class works I can write a real implementation of the Motor interface:</para> <example><title>A real implementation of the Motor interface</title> <programlisting> public class OneSpeedMotor implements Motor { public void move(MoveRequest request) { turnBy(request.getTurn()); advance(request.getDistance()); } [...] } </programlisting> </example> <para>As my tests grow, I can refactor the various mock implementations into a single, more sophisticated MockMotor and use it throughout all the Robot tests; for example:</para> <example><title>Creating a mock motor class</title> <programlisting>public void MockMotor implements Motor { private ArrayList expectedRequests = new ArrayList(); public void move(MoveRequest request) { assert("Too many requests", this.expectedRequests.size() > 0); assertEquals("Should be next request", this.expectedRequests.remove(0), request); } public void addExpectedRequest(MoveRequest request) { this.expectedRequests.add(request); } public void verify() { assertEquals("Too few requests", 0, this.expectedRequests.size()); } }</programlisting> </example> <para>Which makes our tests look like:</para> <example><title>Testing our robot with mock motors</title> <programlisting>public class TestRobot { [...] static final Position ORIGIN = new Position(0, 0); public void setUp() { mockMotor = new MockMotor(); robot = new Robot(mockMotor); robot.setCurrentPosition(ORIGIN); } public void testGotoSamePlace() { robot.goTo(ORIGIN); assertEquals("Should be same", ORIGIN, robot.getPosition()); mockMotor.verify(); } public void testMoveOnePoint() { final Position DESTINATION = new Position(1, 0); mockRobot.addExpectedRequest(new MoveRequest(1, MoveRequest.SOUTH)); robot.goto(DESTINATION); assertEquals("Should be destination", DESTINATION, robot.getPosition()); mockMotor.verify(); } public void testMoveALongWay() { final Position DESTINATION = new Position(34, 71); mockMotor.addExpectedRequests(makeExpectedLongWayMoveRequests()); robot.goto(DESTINATION); assertEquals("Should be destination", DESTINATION, robot.getPosition()); mockMotor.verify(); } } </programlisting> </example> </section> <!-- Factoring out the motor --> <section><title>What does this mean?</title> <para>My code moved in this direction because I was committed to unit testing but didn't want to record and expose unnecessary details about the state of my Robot (the <methodname>getRecentMoveRequests()</methodname> method). As a result, I have better unit tests and a cleaner interface to the Robot class. But even better than that, by following the needs of my tests I have actually ended up improving the object model of the Robot by separating the Robot from its Motor and defining the an abstract interface between the two. I now have the flexibility to subtitute a different Motor implementation, perhaps one that accelerates. Similarly, if I want to track the total distance a Robot travels, I can do so without changing the implementation of either the robot or its motor:</para> <example><title>Tracking distance with the decorator pattern</title> <programlisting> /** A decorator that accumulates distances, then passes the request * on to a real Motor. */ public class MotorTracker implements Motor { private Motor realMotor; private long totalDistance = 0; public MotorTracker(Motor aRealMotor) { realMotor = aRealMotor; } public void move(MoveRequest request) { totalDistance += request.getDistance(); realMotor.move(request); } public long getTotalDistance() { return totalDistance; } } // When constructing the Robot, wrap the implementation of a // Motor that does the work in a MotorTracker. OneSpeedMotor realMotor = new OneSpeedMotor(); MotorTracker motorTracker = new MotorTracker(realMotor); Robot = new Robot(motorTracker); // do some travelling here [...] println("Total distance was: " + motorTracker.getTotalDistance()); </programlisting> </example> <para>Neither changing the Motor implementation nor adding tracking functionality would have been so easy if I had stuck with a testing strategy based on stashing the intermediate route. The next step might be to introduce a <classname>Navigator</classname> object to work out the route, and the Robot would link the two together.</para> <para>Tests based on Mock Objects usually conform to a pattern: setup any state, set expectations for the test, run the target code, and verify that your expectations have been met. This style makes tests easy to work with because they look similar and because all the interactions with an object are local to a test fixture; I have found myself contributing usefully to someone else's code base after only a few minutes with the tests. More importantly, however, I constantly find that the process of deciding what to verify in a test drives me to clarify the relationships between an object and its collaborators. The flex points I add to my code to provide support for testing turn out to be the flex points I need as the use of the code evolves. </para> </section> <!-- What does this mean? --> <section> <title>Conclusions</title> <para> This simple example shows how refactoring tests with some design principles in mind led to the discovery of an unusually fruitful development technique. Using Mock Objects in practice is slightly different. First, the process of writing a test usually involves defining which mock objects are involved, rather than extracting them afterwards. Second, in Java at least, there are the beginnings of a Mock Object library for common classes and APIs. Third, there are now several tools and libraries to help with the construction of Mock Objects. In particular, the <ulink url="http://www.mockobjects.com">www.mockobjects.com</ulink> site includes a library of expectation objects. </para> <para> The rest of this book will show you how to use Mock Objects and Test Driven Design in your development process. We will work through some real-world examples to show how Mock Objects can be used to test Java APIs, drive refactoring and eliminate dependencies on external components. And along the way we will annotate our examples with tips and warnings to help you improve your technique and avoid pitfalls. </para> <para> Mock Objects and Test Driven Design have changed the way we develop code, and changed it noticeably for the better. We hope that you can benefit from these techniques as well and that this book helps you to do so. </para> </section> <!-- Conclusions --> </chapter> |
From: Steve F. <sm...@us...> - 2002-08-09 23:48:44
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv6600/doc/xdocs Modified Files: htmlbook.css Log Message: Moved font out to BODY Index: htmlbook.css =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.css,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- htmlbook.css 5 Aug 2002 01:29:21 -0000 1.4 +++ htmlbook.css 9 Aug 2002 23:48:41 -0000 1.5 @@ -1,10 +1,10 @@ /* htmlbook.css, a stylesheet for htmlbook */ -h1 { text-align: center; font-family: Arial, Helvetica, sans-serif; font-weight: bold} -h2 { font-family: Arial, Helvetica, sans-serif; margin-top: 3%} +BODY { font-family: Arial, Helvetica, sans-serif} +h1 { font-family: Arial, Helvetica, sans-serif; margin-top: 5%; } +h2 { font-family: Arial, Helvetica, sans-serif; margin-top: 3%;} h3 { font-family: Arial, Helvetica, sans-serif; font-style: italic; font-weight: bold} -p { font-family: Arial, Helvetica, sans-serif} -li { font-family: Arial, Helvetica, sans-serif } + .programlisting { margin-left: 5%; } .screen { margin-left: 5%; } .sidebar { border: double black 1px; font-size: 80%; padding: 4px; text-align: center; |
From: Steve F. <sm...@us...> - 2002-08-09 23:48:23
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv6431/doc/xdocs Modified Files: htmlbook.xsl Log Message: Another attempt to fix paths Index: htmlbook.xsl =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.xsl,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- htmlbook.xsl 9 Aug 2002 17:55:48 -0000 1.5 +++ htmlbook.xsl 9 Aug 2002 23:48:20 -0000 1.6 @@ -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: Steve F. <sm...@us...> - 2002-08-09 23:47:45
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv6151/doc/xdocs Added Files: introduction.xml Log Message: Moved from html version --- NEW FILE: introduction.xml --- <part> <title>Introducing Test-Driven Development</title> <chapter><title>Introduction</title> <section> <title>Why I wrote this book</title> <para> My original motivation for this book was that I got tired of explaining what a group of us in London were doing with test-driven development. We had discovered a style of programming that delivered well-structured, reliable code quickly and that seemed to reinforce what we had learned over the years to be good coding habits. Over and over again, we found that developers thought that what we were doing was unnecessary, or too complicated, or too hard to understand, or just weird until we sat down and programmed through some examples with them. This seemed like a very slow way to spread a useful idea, so I started writing to try to speed things up. </para> <para> Since then, I have had my motivation strengthened by the experience of taking over development of a couple of systems written elsewhere. Both systems worked and were developed under time pressure, so clearly the programmers had got something right. Both systems, however, were brittle, with much repetition and needlessly complex logic. One, in particular, had accrued over years and noone had had the time or, perhaps, the inclination to clean up behind themselves. Worse, there were no automated tests (of course, the documentation was out of date) so I could only make changes to the working system by being very cautious and, where possible, retrofitting tests. The contrast with the experience of raw confidence when working with a fully test-driven codebase was so unpleasant that I resolved to do <emphasis>something</emphasis>, however small, to improve the situation. </para> <para> My intentions are entirely selfish, I want to increase the probability by a notch that the next system I have to deal with has been written test-first so that I don't have to do this again. I have an unselfish intention as well, which is that no-one in the next generation of programmers should have to go through the Debug Hell that we old fogeys accept as part of the process of writing software. It <emphasis>is</emphasis> possible to fall off the rails with Test-Driven Development, but it's so rare that we would have to make an effort to preserve the skills when Test-Driven Development becomes the standard way to write code. </para> </section> <section> <title>Who this book is for</title> <para> As with most authors, I'm writing this book for people like me who happen not have seen some of the same things. It's for working programmers who want to learn more about how to do Test-Driven Development in practice. I'm assuming that: </para> <itemizedlist spacing="compact"> <listitem><para> you already think that Test-Driven Development is a good idea, or at least worth investigating; </para></listitem> <listitem><para> you know something about the basics, such as refactoring and the JUnit framework; and, </para></listitem> <listitem><para> if you're not currently working in Java, then you can figure out how to apply these concepts to whatever environment you're working in. </para></listitem> </itemizedlist> </section> <section><title>Organisation</title></section> </chapter> <chapter><title>Test-Driven Development</title> <section><title>Introduction and simple example</title></section> <section><title>A quick tour of JUnit</title> </section> </chapter> &chp_how_mocks_happened; <chapter><title>Expectations</title> </chapter> </part> |
From: Steve F. <sm...@us...> - 2002-08-09 23:47:25
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv5970/doc/xdocs Modified Files: doc-book.xml Log Message: Another attempt at sorting out the pathnames Added draft outline Index: doc-book.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/doc-book.xml,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- doc-book.xml 9 Aug 2002 18:46:01 -0000 1.4 +++ doc-book.xml 9 Aug 2002 23:47:18 -0000 1.5 @@ -1,11 +1,14 @@ <?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 chp_random SYSTEM "file:@docpath@/random.xml"> - <!ENTITY notes SYSTEM "file:@docpath@/notes.xml"> + <!ENTITY % extra_entities SYSTEM "file://@docpath@/extra_entities.xml"> + + <!ENTITY part_introduction SYSTEM "file://@docpath@/introduction.xml"> + <!ENTITY chp_how_mocks_happened SYSTEM "file://@docpath@/how_mocks_happened.xml"> + + <!ENTITY part_testing_guis SYSTEM "file://@docpath@/testing_guis_1.xml"> + <!ENTITY chp_random SYSTEM "file://@docpath@/random.xml"> + <!ENTITY notes SYSTEM "file://@docpath@/notes.xml"> %docbook; %extra_entities; ]> @@ -29,10 +32,53 @@ <holder>Steve Freeman</holder> </copyright> </bookinfo> - - &testing_guis1; - <part><title>Living With Unit Testing</title> + + &part_introduction; + + <part> + <title>Larger examples</title> + + <chapter> + <title>Servlets</title> + </chapter> + + <chapter> + <title>JDBC</title> + </chapter> + </part> + + &part_testing_guis; + + <part> + <title>Living with Unit Tests</title> + + <chapter><title>Test organisation</title></chapter> + + <chapter><title>Test smells</title></chapter> + + <chapter><title>Retrofitting unit tests</title></chapter> + </part> + + <part><title>Some other languages</title> + + <chapter> + <title>C++</title> + <para>with Workshare?</para> + </chapter> + + <chapter><title>bash</title></chapter> + + <chapter><title>Dynamic and metaprogramming</title></chapter> + </part> + + <part> + <title>Other patterns</title> &chp_random; </part> + + <part> + <title>Closing</title> + </part> + ¬es; </book> |