Friday, October 23, 2009

A QUICK EXAMPLE









































Prev don't be afraid of buying books Next






























A QUICK EXAMPLE



Let's take a peek into the development of the
project from later in the book. We have a Movie class
which now needs to accept multiple ratings (e.g., 3 as in "3 stars
out of 5") and give access to the average.



As we go through the example, we will be
alluding to a metaphor for the TDD flow originated by William Wake:
The TDD Traffic Light[URL 9][URL 61].



We start by writing a test, and we start the
test by making an assertion that we want to be true:




public void testRating() {
assertEquals("Bad average rating.",4,starWars.getAverageRating());
}






Now we need to set the stage for that assertion
to be true. To do that we'll add some rating to the
Movie:




public void testRating() {
starWars.addRating(3);
starWars.addRating(5);
assertEquals("Bad average rating.",4,starWars.getAverageRating());
}






Finally, we need to create the Movie
instance we are working with:




public void testRating() {
Movie starWars = new Movie("Star Wars");
starWars.addRating(3);
starWars.addRating(5);
assertEquals("Bad average rating.",4,starWars.getAverageRating());
}






When we compile this, the compiler complains
that addRating(int) and getAverageRating() are
undefined. This is our yellow light. Now we make it compile by
adding the following code to Movie:




public void addRating(int newRating) {
}




public int getAverageRating() {
return 0;
}






Note that since we are using Java, we must
provide a return value for getAverageRating() since we've
said it returns an int.



Now it compiles, but the test fails. This is the
red light (aka red bar). This term is derived by the JUnit
interfaces that present a progress bar that advances as tests are
run. As long as all tests pass, the bar is green. As soon as a test
fails, the bar turns red and remains red. The message we get
is:




Bad average rating. expected:<4> but was:<0>






Now we have to make the test pass. We add code
to getAverageRating() to make the test pass:




public int getAverageRating() {
return 4;
}






Recompile and rerun the test. Green light! Now
we refactor to remove the duplication and other smells that we
introduced when we made the test pass.



You're probably thinking "Duplication.. . what
duplication?" It's not always obvious at first. We'll start by
looking for constants that we used in making the test work. Sure
enough, look at getAverageRating().It returns a constant.
Remember that we set the test up to get the desired result. How did
we do that? In this case we gave the movie two ratings: 3 and 5.
The average result is the 4 that we are returning. So, that 4 is
duplicated. We provide the information required to compute it, as
well as returning it as a constant. Returning a constant when we
can compute its value is a form of duplication. Let's get rid of
it.



Our first step is to rewrite that constant into
something related to the provided information:




public int getAverageRating() {
return (3 + 5) / 2;
}






Compile and run the tests. We're OK. We have the
courage to continue. The 3 and 5 are duplicate with the arguments
to addRating() so let's capture them. Since we add the
constants we can simply accumulate the arguments. First we add a
variable to accumulate them:




private int totalRating = 0;












Then we add some code to
addRating():




public void addRating(int newRating) {
totalRating += newRating;
}






Now we use it in
getAverageRating():




public int getAverageRating() {
return totalRating / 2;
}






Compile, test, it works! We're not finished yet,
though. While we were refactoring we introduced another constant:
the 2 in getAverageRating().The duplication here
is a little subtler. The 2 is the number ratings we added, i.e.,
the number of times addRating() was called. We need to
keep track of that in order to get rid of the 2.



Like before, start by defining a place for
it:




private int numberOfRatings = 0;






Compile, run the tests, green. Now, increment it
every time addRating() is called:




public void addRating(int newRating) {
totalRating += newRating;
numberOfRatings++;
}






Compile, run the tests, green. OK, finally we
replace the constant 2 with numberOfRatings:




public int getAverageRating() {
return totalRating / numberOfRatings;
}






Compile, run the tests, green. OK, we're done.
If we want to reinforce our confidence in what we did, we can add
more calls to addRating() and check against the
appropriate expected average. For example:




public void testLotsOfRatings()
{

Moviegodzilla = new Movie("Godzilla");
godzilla.addRating(1);
godzilla.addRating(5);
godzilla.addRating(1);
godzilla.addRating(2);
assertEquals("Bad average rating.",2,godzilla.getAverageRating());
}












I need to underline the fact that I recompiled
and ran the tests after each little change above. This cannot be
stressed enough. Running tests after each small change gives us
confidence and reassurance. The result is that we have courage to
continue, one little step at a time. If at any point a test failed
we know exactly what change caused the failure: the last one. We
back it out and rerun the tests. The tests should pass again. Now
we can try again... with courage.



The above example shows one school of thought
when it comes to cleaning up code. In it we worked to get rid of
the duplication that was embodied in the constant. Another school
of thought would leave the constant 4 in place and write another
test that added different ratings, and a different number of them.
This second test would be designed to require a different returned
average. This would force us to refactor and generalize in order to
get the test to pass.



Which approach should you take? It really
depends on how comfortable you are with what you are attempting.
Remember that you do have the test to safeguard you. As long as the
test runs, you know that you haven't broken anything. In either
case you will want to write that second test: either to drive the
generalization, or to verify it.















































Amazon






No comments:

Post a Comment