From: Nat P. <np...@us...> - 2002-08-08 15:03:22
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv1957 Modified Files: random.xml Log Message: Merged Steve's changes. Rewrote some text, finished Summary section. Index: random.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/random.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- random.xml 7 Aug 2002 21:53:44 -0000 1.2 +++ random.xml 8 Aug 2002 15:03:17 -0000 1.3 @@ -1,159 +1,164 @@ -<chapter> - <title>Random Acts</title> +<chapter status="draft"> +<title>Random Acts</title> - <section> - <title>Introduction</title> - <remark>Expand this</remark> - - <para> - Pseudo-random behaviour is used in many applications. - In games it is used to portray natural behaviours that are too complex to - simulate accurately, or to add variety to behaviours that are too predictable - when simulated algorithmically. - </para> - - <para> - How do we test randomness? - </para> - - </section> <!-- Introduction --> - - <section> - <title>Come Rain...</title> - - <para> - I have been contracted to write a game that simulates the strategy of - Formula One motor racing. - My customer tells me that the weather plays has an important effect on - F1 strategy, so my game will have to simulate the weather somehow. - However, she wants the game to run in a web browser, and that means - writing it as a Java applet. - I'm going to rule out a realistic weather simulation — I'm going to - need all those CPU cycles to simulate the cars and drivers — and - instead randomly generate weather effects. - </para> - - <para> - The first story I have is that the player must choose different tyres - depending on whether it is raining or not. Most of the time it will - be sunny but it should rain on every one out of every five races, - chosen at random. - Time to write a test, but how do I test a random event? - The weather object is obviously going to use a random number generator - to test the probability of rain. If that random number generator is - completely encapsulated within the weather object, I cannot override - the randomness to cause the behaviour I want to test. - Therefore, I need to mock that random number generator in my tests to - feed in fixed values, and pass the random number generator to the - weather object's constructor. - </para> - - <programlisting lang="java"> -public void testRandomRain() { - MockRandom rng = new MockRandom(); - - Weather weather = new Weather( rng ); - - rng.setNextDouble( 0.0 ); - weather.randomize(); - assertTrue( "is raining", weather.isRaining() ); - - rng.setNextDouble( Weather.CHANCE_OF_RAIN ); - weather.randomize(); - assertTrue( "is not raining", !weather.isRaining() ); - - rng.setNextDouble( 1.0 ); - weather.randomize(); - assertTrue( "is not raining", !weather.isRaining() ); -}</programlisting> - - <para> - Here's the <classname>MockRandom</classname> class used by the test: - </para> +<chapterinfo> + <author>Nat Pryce</author> + <copyright><year>2002</year> <holder>Nat Pryce</holder></copyright> +</chapterinfo> + +<section> +<title>Introduction</title> + +<remark>Expand this</remark> + +<para> +Pseudo-random behaviour is used in many applications. +In games it is used to portray natural behaviours that are too complex to +simulate accurately, or to add variety to behaviours that are too predictable +when simulated algorithmically. +</para> + +<para> +How do we test randomness? +</para> + +</section> + +<section> +<title>Come Rain...</title> + +<para> +I have been contracted to write a game that simulates the strategy of +Formula One motor racing. +My customer tells me that the weather plays an important effect +in F1 strategy, so my game will have to simulate the weather somehow. +However, she wants the game to run in a web browser, and that +means writing it as a Java applet. +I'm going to rule out a realistic weather simulation — I'm going to +need all those CPU cycles to simulate the cars and drivers — and +instead randomly generate weather effects. +</para> + +<para> +The first story I have is that the player must choose different tyres +depending on whether it is raining or not. Most of the time it will +be sunny but it should rain on every one out of every five races, +chosen at random. +Time to write a test, but how do I test a random event? +The weather object is obviously going to use a random number generator +to test the probability of rain. If that random number generator is +completely encapsulated within the weather object, I cannot override +the randomness to cause the behaviour I want to test. +Therefore, I need to mock that random number generator in my tests to +feed in fixed values, and pass the random number generator to the +weather object's constructor. +</para> - <programlisting> -public class MockRandom extends Random +<programlisting lang="java">public void testRandomRain() { + MockRandom rng = new MockRandom(); + + Weather weather = new Weather( rng ); + + rng.setNextDouble( 0.0 ); + weather.randomize(); + assertTrue( "is raining", weather.isRaining() ); + + rng.setNextDouble( Weather.CHANCE_OF_RAIN ); + weather.randomize(); + assertTrue( "is not raining", !weather.isRaining() ); + + rng.setNextDouble( 1.0 ); + weather.randomize(); + assertTrue( "is not raining", !weather.isRaining() ); +}</programlisting> + +<para> +Here's the <classname>MockRandom</classname> class used by the test: +</para> + +<programlisting>public class MockRandom + extends Random { private double nextDouble = 0.0; - + public void setNextDouble( double d ) { nextDouble = d; } - + public double nextDouble() { return nextDouble; } -}</programlisting> +} +</programlisting> - <para> - And now we can write a Weather class that passes the tests: - </para> +<para> +And now we can write a Weather class that passes the tests: +</para> - <programlisting lang="java"> -public class Weather +<programlisting lang="java">public class Weather { public static double CHANCE_OF_RAIN = 0.2; - - + + private Random rng; private boolean isRaining = false; - + public Weather( Random rng ) { this.rng = rng; } - + public boolean isRaining() { return isRaining; } - + public void randomize() { isRaining = rng.nextDouble() < CHANCE_OF_RAIN; } }</programlisting> - </section> <!-- Come rain --> +</section> + - <section> - <title>...Or Shine</title> +<section> +<title>...Or Shine</title> - <para> - My customer now tells me that ground temperature is also - important to race strategy. - The player should choose different tyre compounds depending on the - temperature. Now my weather object must choose a temperature and whether - it is raining, both at random. - The random temperature will be chosen from a range of 20°C and 30°C. - But, the ground should be on average half the temperature when it is raining - compared to when it is sunny. - </para> - - <para> - Again, let's write a test. Actually, I think we need - two tests, one to test that the temperature - is selected from within the forecast range when it is sunny, - and another to test that it is half the sunny temperature when raining. - Let's start with the former. Again we need to mock the random number - generator, but this time the <methodname>randomize</methodname> method - will get <emphasis>two</emphasis> random numbers. We need to change our - <classname>MockRandom</classname> class to mock a stream of random - numbers, rather than just one: - </para> +<para>My customer now tells me that ground temperature is also +important to race strategy. +The player should choose different tyre compounds depending on the +temperature. Now my weather object must choose a temperature and whether +it is raining, both at random. +The random temperature will be chosen from a range of 20°C and 30°C. +But, the ground should be on average half the temperature when it is raining +compared to when it is sunny. +</para> + +<para> +Again, let's write a test. Actually, I think we need +two tests, one to test that the temperature +is selected from within the forecast range when it is sunny, +and another to test that it is half the sunny temperature when raining. +Let's start with the former. Again we need to mock the random number +generator, but this time the <methodname>randomize</methodname> method +will get <emphasis>two</emphasis> random numbers. We need to change our +<classname>MockRandom</classname> class to mock a stream of random +numbers, rather than just one: +</para> - <programlisting> -public class MockRandom extends Random +<programlisting>public class MockRandom + extends Random { private double[] nextDoubles = {0.0}; private int nextIndex = 0; - + public void setNextDouble( double d ) { setNextDoubles( new double[]{ d } ); } - + public void setNextDoubles( double[] d ) { nextDoubles = d; nextIndex = 0; } - + public double nextDouble() { double result = nextDoubles[nextIndex]; nextIndex = (nextIndex + 1) % nextDoubles.length; @@ -161,19 +166,18 @@ } }</programlisting> - <para> - Ok, the new <classname>MockRandom</classname> doesn't affect our existing - tests, so we can go on to write the test for random temperature when sunny: - </para> +<para> +Ok, the new <classname>MockRandom</classname> doesn't affect our existing +tests, so we can go on to write the test for random temperature when sunny: +</para> - <programlisting> -public void testRandomTemperatureSunny() { +<programlisting>public void testRandomTemperatureSunny() { MockRandom rng = new MockRandom(); final double SUNNY = 1.0; - + Weather weather = new Weather( rng ); - + rng.setNextDoubles( new double[] { SUNNY, 0.0 } ); weather.randomize(); assertEquals( "should be min temperature", @@ -191,58 +195,56 @@ Weather.MAX_TEMPERATURE, weather.getTemperature(), 0.0 ); }</programlisting> - <para> - And write code to pass that test: - </para> +<para> +And write code to pass that test: +</para> - <programlisting> -public class Weather +<programlisting>public class Weather { public static double CHANCE_OF_RAIN = 0.2; <emphasis>public static double MIN_TEMPERATURE = 20; public static double MAX_TEMPERATURE = 30;</emphasis> - + private Random rng; private boolean isRaining = false; <emphasis>private double temperature = MIN_TEMPERATURE;</emphasis> - - public Weather( Random aRng ) { - rng = aRng; + + public Weather( Random rng ) { + this.rng = rng; } - + public boolean isRaining() { return isRaining; } - + <emphasis>public double getTemperature() { return temperature; }</emphasis> - + public void randomize() { isRaining = rng.nextDouble() < CHANCE_OF_RAIN; - <emphasis>temperature = MIN_TEMPERATURE + + <emphasis>temperature = MIN_TEMPERATURE + rng.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE);</emphasis> } }</programlisting> - <para> - Now for the temperature when it is raining. The test will look very similar - the the one I just wrote, except that it expect the temperatures to be - half those when sunny. - </para> +<para> +Now for the temperature when it is raining. The test will look very similar +the the one I just wrote, except that it expect the temperatures to be +half those when sunny. +</para> - <programlisting> -public void testRandomTemperatureRaining() { +<programlisting>public void testRandomTemperatureRaining() { MockRandom rng = new MockRandom(); final double RAIN = 0.0; - + Weather weather = new Weather( rng ); - + rng.setNextDoubles( new double[] { RAIN, 0.0 } ); weather.randomize(); assertEquals( "should be min rainy temperature", Weather.MIN_TEMPERATURE/2, weather.getTemperature(), 0.0 ); - + rng.setNextDoubles( new double[] { RAIN, 0.5 } ); weather.randomize(); assertEquals( "should be average rainy temperature", @@ -255,67 +257,63 @@ Weather.MAX_TEMPERATURE/2, weather.getTemperature(), 0.0 ); }</programlisting> - <para> - Now I'll change the <classname>Weather</classname>'s - <methodname>randomize</methodname> to half the temperature when it is raining: - </para> - - <programlisting> -public void randomize() { - temperature = MIN_TEMPERATURE + - rng.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE); +<para> +Now I'll change the <classname>Weather</classname>'s +<methodname>randomize</methodname> to half the temperature when it is raining: +</para> +<programlisting>public void randomize() { + temperature = MIN_TEMPERATURE + + rng.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE); + isRaining = rng.nextDouble() < CHANCE_OF_RAIN; if( isRaining ) temperature *= 0.5; }</programlisting> - <para> - That was easy! I'll just run my tests and... whoops! The - <methodname>testRandomTemperatureRaining</methodname> test failed. - Not only that, my <methodname>testRandomTemperatureSunny</methodname> test - failed as well! Why did that happen? The behaviour I added to the - <methodname>randomize</methodname> method should not have had an affect - when it was not raining. - </para> - - </section> <!-- Come shine --> - - <section> - <title>Test Smell: Order Shouldn't Matter</title> - - <para> - Actually, looking at it again, I realise that I swapped the order - of that statements that randomized the rain and temperature. - The tests now initialise the stream of mock random numbers in the wrong - order. This is not good: tests should not be tied to the internal - implementation details of the class. They should specify only its externally - visible behaviour. How can I make my tests less brittle? - </para> - - <para> - There should not be any externally visible dependency between randomising - the temperature and randomising the rain. A way to remove the dependency - is to pass <emphasis>two</emphasis> random number generators to the - <classname>Weather</classname> class, one for the temperature and one for - the rain. My tests can then mock each generator independently to force - a particular outcome, no matter what order the <classname>Weather</classname> - samples the generators. Here's the last test rewritten with two generators: - </para> +<para>That was easy! I'll just run my tests and... whoops! The +<methodname>testRandomTemperatureRaining</methodname> test failed. +Not only that, my <methodname>testRandomTemperatureSunny</methodname> test +failed as well! Why did that happen? The behaviour I added to the +<methodname>randomize</methodname> method should not have had an affect +when it was not raining. +</para> + +</section> + +<section> +<title>Test Smell: Order Shouldn't Matter</title> + +<para>Actually, looking at it again, I realise that I swapped the order +of that statements that randomized the rain and temperature. +The tests now initialise the stream of mock random numbers in the wrong +order. This is not good: tests should not be tied to the internal +implementation details of the class. They should specify only its externally +visible behaviour. How can I make my tests less brittle? +</para> + +<para> +There should not be any externally visible dependency between randomising +the temperature and randomising the rain. A way to remove the dependency +is to pass <emphasis>two</emphasis> random number generators to the +<classname>Weather</classname> class, one for the temperature and one for +the rain. My tests can then mock each generator independently to force +a particular outcome, no matter what order the <classname>Weather</classname> +samples the generators. Here's the last test rewritten with two generators: +</para> - <programlisting> -public void testRandomTemperatureRaining() { +<programlisting>public void testRandomTemperatureRaining() { <emphasis>MockRandom rain_rng = new MockRandom(); rain_rng.setNextDouble(0.0); - + MockRandom temp_rng = new MockRandom(); - + Weather weather = new Weather( rain_rng, temp_rng ); - + temp_rng.setNextDouble( 0.0 );</emphasis> weather.randomize(); assertEquals( "should be min rainy temperature", Weather.MIN_TEMPERATURE/2, weather.getTemperature(), 0.0 ); - + <emphasis>temp_rng.setNextDouble( 0.5 );</emphasis> weather.randomize(); assertEquals( "should be average rainy temperature", @@ -328,162 +326,160 @@ Weather.MAX_TEMPERATURE/2, weather.getTemperature(), 0.0 ); }</programlisting> - <para> - I can rewrite the <methodname>testRandomTemperatureSunny</methodname> - test in a similar way and I also have to change the - <methodname>testRandomRain</methodname> test to instantiate the - <classname>Weather</classname> object with two random number generators; - we'll skip over the code to save trees. - </para> - - <para> - Now I have failing tests for the behaviour I want to implement, so I - need to change my Weather class to use two random number generators: - </para> +<para> +I can rewrite the <methodname>testRandomTemperatureSunny</methodname> +test in a similar way and I also have to change the +<methodname>testRandomRain</methodname> test to instantiate the +<classname>Weather</classname> object with two random number generators; +we'll skip over the code to save trees. +</para> + +<para> +Now I have failing tests for the behaviour I want to implement, so I +need to change my Weather class to use two random number generators: +</para> - <programlisting> -public class Weather +<programlisting>public class Weather { public static double CHANCE_OF_RAIN = 0.2; - public static double MIN_TEMPERATURE = 20; // degrees C - public static double MAX_TEMPERATURE = 30; // degrees C - + public static double MIN_TEMPERATURE = 20; + public static double MAX_TEMPERATURE = 30; + <emphasis>private Random tempRandom, rainRandom;</emphasis> private boolean isRaining = false; private double temperature = MIN_TEMPERATURE; - + <emphasis>public Weather( Random rainRandom, Random tempRandom ) { this.tempRandom = tempRandom; this.rainRandom = rainRandom; }</emphasis> - + [...] - + public void randomize() { - temperature = MIN_TEMPERATURE + + temperature = MIN_TEMPERATURE + <emphasis>tempRandom</emphasis>.nextDouble() * (MAX_TEMPERATURE-MIN_TEMPERATURE); - + isRaining = <emphasis>rainRandom</emphasis>.nextDouble() < CHANCE_OF_RAIN; if( isRaining ) temperature *= 0.5; } }</programlisting> - <tip> - Only test the order in which an object calls methods of other objects - if that is an important aspect of your object's publically visible - behaviour. If it is unimportant, your tests will be brittle if your - they expect one particular order. - </tip> - - <para> - Finally, my <classname>MockRandom</classname> class now contains behaviour - that I don't use, and that I've realised is a bad idea. I'll discard - that code by restoring the original version of the class from my source - code repository. - </para> - - </section> <!-- Test Smell --> - - <section> - <title>Refactoring: Too Many Arguments</title> - <para> - My customer is happy. However, she tells me, in Formula One, teams - use tyres with different treads depending on how wet the track is, so - our weather class needs to simulate both if it is raining, and - how wet the track is. Another important element of Formula One strategy - is changing tyres when the weather changes, so the rain should start and - stop at random intervals, and the track should become wetter when it is - raining and dry off when it is sunny. - </para> - - <para> - All this is straightforward to implement, but my nose is twitching: - my code, although functional, is smelly. - I can foresee that as I add functionality to the - <classname>Weather</classname> class, the implementation will become - increasingly awkward because there will be too many random number generators. - In particular each time I add more random behaviour I will have to change - all the tests because the signature of the constructor will have changed, - and the constructor will end up with far too many parameters: - </para> +<tip> +Only test the order in which an object calls methods of other objects +if that is an important aspect of your object's publically visible +behaviour. If it is unimportant, your tests will be brittle if your +they expect one particular order. +</tip> + +<para> +Finally, my <classname>MockRandom</classname> class now contains behaviour +that I don't use, and that I've realised is a bad idea. I'll discard +that code by restoring the original version of the class from my source +code repository. +</para> + +</section> + + +<section> +<title>Refactoring: Too Many Arguments</title> +<para> +My customer is happy with progress so far but tells me that Formula One +teams use tyres with different treads depending on how wet the track is. +She wants the player to have to make the same decisions so my weather class +will have to randomise how heavily the rain is falling. +Another important element of Formula One strategy is changing tyres when +the weather changes, so the rain should start and stop at random intervals, +and the track should become wetter when it is raining and dry off when it +is sunny. +</para> + +<para> +All this is straightforward to implement, but my nose is twitching: +my code, although functional, is smelly. +I can foresee that as I add functionality to the +<classname>Weather</classname> class, the implementation will become +increasingly awkward because I will have to pass in more random number +generators. +Each time I add more random behaviour I will have to change +all the tests because the signature of the constructor will have changed, +and the constructor will end up with far too many parameters: +</para> - <programlisting> -public Weather( Random rainRandom, Random tempRandom, Random wetnessRandom, +<programlisting>public Weather( Random rainRandom, Random tempRandom, Random wetnessRandom, Random rainDurationRandom, Random dryDurationRandom ) { [...] }</programlisting> - <para> - I recognise this "code smell". Every time I see a method that takes a lot - of arguments, I know that there is a new concept waiting to be extracted. - Just as the method - <methodname>drawRectangle( int x, int y, int width, int height )</methodname> - indicates that a Rectangle class should be factored out and the method - replaced by <methodname>draw( Rectangle r )</methodname>, so all those - <classname>Random</classname> arguments indicate that there is an - weather-specific source of randomness waiting to be extracted. - It's time to don my refactoring hat and clear up this mess now; - leaving it any later will just make more work. The first thing I need - to do is define an interface for a weather-specific source of randomness: - </para> +<para> +I recognise this "code smell". Every time I see a method that takes a lot +of arguments, I know that there is a new concept waiting to be extracted. +Just as the method +<methodname>drawRectangle( int x, int y, int width, int height )</methodname> +indicates that a Rectangle class should be factored out and the method +replaced by <methodname>draw( Rectangle r )</methodname>, so all those +<classname>Random</classname> arguments indicate that there is a +weather-specific source of randomness waiting to be extracted. +It's time to don my refactoring hat and clear up this mess now; +leaving it any later will just make more work. The first thing I need +to do is define an interface for a weather-specific source of randomness: +</para> - <programlisting> -public interface WeatherRandom +<programlisting>public interface WeatherRandom { boolean nextIsRaining(); double nextTemperature(); }</programlisting> - <para> - I don't have to implement this interface right now. - I can test the <classname>Weather</classname> class by mocking the interface, - and then test and write an implementation once the - <classname>Weather</classname> tests are passing. - Our new <classname>WeatherRandom</classname> class simplifies our tests - greatly. We no longer have to test that the <classname>Weather</classname> - class compares probabilities against random numbers correctly; that will - be the responsibility of the real implementation of - <classname>WeatherRandom</classname>. Instead we just have to test that - the <classname>Weather</classname> class stores the random weather and - calculates a cooler temperature when it is raining. Here's our - <methodname>testRandomTemperatureRaining</methodname> test: - </para> +<para> +I don't have to implement this interface right now. +I can test the <classname>Weather</classname> class by mocking the interface, +and then test and write an implementation once the +<classname>Weather</classname> tests are passing. +Our new <classname>WeatherRandom</classname> class simplifies our tests +greatly. We no longer have to test that the <classname>Weather</classname> +class compares probabilities against random numbers correctly; that will +be the responsibility of the real implementation of +<classname>WeatherRandom</classname>. Instead we just have to test that +the <classname>Weather</classname> class stores the random weather and +calculates a cooler temperature when it is raining. Here's our +<methodname>testRandomTemperatureRaining</methodname> test: +</para> - <programlisting> -public void testRandomTemperatureRaining() { +<programlisting>public void testRandomTemperatureRaining() { final double TEMPERATURE = 20; - + MockWeatherRandom rng = new MockWeatherRandom() { public boolean nextIsRaining() { return true; } public double nextTemperature() { return TEMPERATURE; } }; - + Weather weather = new Weather( rng ); - + weather.randomize(); - assertEquals( "temperature", + assertEquals( "temperature", TEMPERATURE/2.0, weather.getTemperature(), 0.0 ); }</programlisting> - <para> - Now I have to modify the <classname>Weather</classname> class to - use a <classname>WeatherRandom</classname> to pass the tests: - </para> +<para> +Now I have to modify the <classname>Weather</classname> class to +use a <classname>WeatherRandom</classname> for it to pass the tests: +</para> - <programlisting> -public class Weather +<programlisting>public class Weather { <emphasis>private WeatherRandom random;</emphasis> private boolean isRaining = false; private double temperature = 0.0; - + <emphasis>public Weather( WeatherRandom random ) { - this.random= random; + this.random = random; }</emphasis> - + [...] - + public void randomize() { <emphasis>temperature = random.nextTemperature(); isRaining = random.nextIsRaining();</emphasis> @@ -491,22 +487,21 @@ } }</programlisting> - <para> - And finally I need to test and implement a real - <classname>WeatherRandom</classname> that generates random weather - using a random number generator. - Each of the <classname>WeatherRandom</classname> methods can be tested - individually using a MockRandom object, just as we did in our earlier - tests of the <classname>Weather</classname> class. Because each method - makes one call to the random number generator, no internal - implementation details leak out into our tests. - </para> +<para> +And finally I need to test and implement a real +<classname>WeatherRandom</classname> that generates random weather +using a random number generator. +Each of the <classname>WeatherRandom</classname> methods can be tested +individually using a MockRandom object, just as we did in our earlier +tests of the <classname>Weather</classname> class. Because each method +makes one call to the random number generator, no internal +implementation details leak out into our tests. +</para> - <programlisting> -public void testNextIsRaining() { +<programlisting>public void testNextIsRaining() { MockRandom rng = new MockRandom(); WeatherRandom weather_random = new DefaultWeatherRandom(rng); - + rng.setNextDouble( 0.0 ); assertTrue( "is raining", weather_random.nextIsRaining() ); @@ -517,16 +512,16 @@ assertTrue( "is not raining", !weather_random.nextIsRaining() ); }</programlisting> - <para> - I can now go on to implement the additional random behaviour requested - by my customer. I will define each additional random effect as a method - in the <classname>WeatherRandom</classname> interface. I will also define - a sensible default result in the <classname>MockWeatherRandom</classname> - so that tests that do not care about the effect do not have to be changed. - </para> +<para> +I can now go on to implement the additional random behaviour requested +by my customer. I will define each additional random effect as a method +in the <classname>WeatherRandom</classname> interface. I will also define +a sensible default result in the <classname>MockWeatherRandom</classname> +so that tests that do not care about the effect do not have to be changed. +</para> - <programlisting> -public interface WeatherRandom +<programlisting> +public interface WeatherRandom { boolean nextIsRaining(); double nextTemperature(); @@ -535,34 +530,42 @@ double nextDryDuration();</emphasis> }</programlisting> - </section> <!-- Too many arguments --> - - <section> - <title>What Have We Learned?</title> +</section> - <remark>Expand this</remark> +<section> +<title>What Have We Learned?</title> - <para> - Test random behaviour by pulling the random number generator out of the - object being tested and mocking it. - </para> - - <para> - Mocking random behaviour can expose implementation details by assuming - how individual elements of a random number sequence will be used by - the class under test. This breaks encapsulation and makes tests brittle. - Avoid brittle tests by "parallelizing" your random number streams. - Initialise your objects with multiple sources of randomness that can be - mocked independently to test specific behaviours. - </para> - - <para> - Define application-specific random generators, rather than passing multiple - random number generators into your class, so that changes to the random - behaviour of your class do not cause changes to ripple through your class - and all of its tests. - </para> +<para> +Hopefully this chapter has shown you that it is possible to test random +behaviour without referring to your old college statistics text books. +Random behaviour can be divided into two parts, the part that generates +randomness, and the part that acts upon that randomness. By dividing the +two parts into separate objects and defining a clean interface between +the two, you can mock the randomness and feed deterministic values into +the object you want to test. +</para> + +<para> +However, as you have seen, mocking random behaviour can expose implementation +details when tests contain assumptions about how the elements of a random +number sequence are used by the class being tested. +These assumptions break encapsulation and make the tests brittle. +You can avoid brittle tests by "parallelizing" your random number streams: +initialise your objects with multiple sources of randomness that can be +mocked independently to test specific behaviours. +</para> + +<para> +Parallelizing the sources of randomness by passing an object multiple +independent random number streams can also make your tests brittle. +Changes to the internal behaviour of the class will require changes +to the class' public interface. This brittleness can be avoided by +replacing multiple random number generators with application specific +sources of randomness. Changes to the random behaviour of your +class are limited to the application specific random generator and do +not cause a cascade of changes to your class interface and all of its tests. +</para> - </section> <!-- what have we learned --> +</section> </chapter> |