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>
|