TddExample

From Epowiki

Jump to: navigation, search

The first step towards testing everything is to decide you are going to test everything. -- Kent Beck

This document provides an example of the test driven development process using a custom test framework called CppUnit that worked on an ancient embedded system. It won't work under your system, but the examples are still good for illustrative purposes. Hopefully the meaning of the various test framework components will be obvious.


Contents

Introduction

This document tries to teach Test Driven Development and CppUnit by example. The example chosen is making a Radio model for a GUI. It is complicated enough we can show the full complexity of development, yet simple enough we won't get overwhelmed in development details.

You'll first need to read Unit Testing And Test Driven Development.

Problem Setup

A client wants a radio object that will be used as the backend (model) for a GUI radio program. But let's not worry about the GUI part yet, let's just get the basic radio model working. We have all used radios. Should be simple, right? <P>

An approach to figuring out what to do is the Planning Game. <P>


Creating Use Cases

Use cases are the programmer level requirements. The can be derived from a formal requirements list, your own head, from talking with customers, or from random dart throwing.

  1. Select a station.
  2. Change the volume.
  3. See what the current volume is.
  4. See what the current station is.
  5. Scan for the next station that can be heard.
  6. Keep a list of favorite stations.

Now i'm sure you have all sorts of additions and questions, but let's stop here and see where it goes. <P>

Ok, people always want to bring up turning the radio on and off. This is a software representation of a radio, not a physical radio, is there really a reason yet to suppose it ever needs to be off? <P>

The next thing people bring up is AM/FM in station selection. Does it really matter from a software perspective? <P>

Notice we are approaching a radio from a particular problem space. We are not saying, hey, it's a radio, let's go see how a radio is made and duplicate all the parts in software. We are not modeling physical reality. We are not implementing our ideas of how a radio should work. <P>

We are instead trying to implement the requirements derived from how the software is to be used, as specified by the use cases, and just implement that. It's very tempting to insert ones own notions of what should happen into the design even though they aren't justified by the uses cases. Always refer to the use cases. You can certainly change the requirements/uses cases if you wish, but don't silenetly change the code to insert your pet idea of what a radio is or should do. <P>


Prioritizing Use Cases

Use cases need to be prioritized into implementation order. The order as listed looks good enough. Nothing seems particularly risky. If it did we would research and tackle that first as in Worst Things First. Or we just get started as in Easiest Thing First Hardest Second.

CRC (Class-Responsibility-Collaborator) Cards

CRC is a design technique where people sit around a table and work out a design. Team design will almost always be better than the lone wolf sitting alone with their own thoughts. <P>

The various objects are represented by cards. The cards are moved around the table to help represent the way the design works -- related objects are put close together and so on. Notes are often kept on the cards to keep track of proposed class names, the responsibilities of the classes, and the classes they collaborate with. <P>

After a CRC design is done, the result is that the people involved in the design understand more about what they're going to do. Cards aren't strictly necessary. Everything can be drawn out on a whiteboard and then later translated to UML or even to real live classes.


First, We Need to Setup the Test Directory

Before implementing the first use case we need to write a test, but we don't have a test environment yet! See your unit test library documentation. <P>

Let's call this test Radio and it will be implemented as a library under directory sw/component/Radio. <P>

The test is located under sw/test/component/Radio to make it parallel to its place in the source tree. For now let's just have one TestCase called RadioTest. <P>

Return back here after you have created the test infrastructure. Previously here i gave instruction on how to do this, but nobody care's how to setup a framework you won't be able to use. <P>

Tick...tick...tick...tick...tick... <P>

Ok, now we should have and test environment in which we can start adding and running tests. Yeah! <P>

The source for all the tests can be found at here.


Let's Implement the First Use Case

The completed first use case can be found under Use Case One. Intermediate code snippets are included in this document, but we can't show every change. <P>

The first use case is Select a station. <P>

At this point you are going to have to decide how to balance You Aren't Going to Need It, Do the Simplest Thing that Could Possibly Work, Simple Versus Right, Just Sufficient Implementation, and Coupling and Cohesion.

I have not built a radio before so i am going to take it slow. In a domain in which i have more experience i would feel comfortable creating more abstractions sooner and taking on bigger chunks at a time. Also, by going slow we get to see if listening to the code guides us to any interesting solutions. We also get a lot more examples of refactoring :-) <P>

Add The Test

First, let's setup the test. The obvious name for our test is SelectStationTest. <P>

  1. The example code has a test called YourTest all setup. This is our first test so we can just change all instances of YourTest to SelectStationTest.
  2. You have to change/add the method name in the header file RadioTest.h.
  3. The method name in RadioTest.cpp must be added/changed.
  4. The method name in RadioTest::runTest must be added/changed.
  5. The method name in RadioTestSuite::RadioTestSuite must be added/changed.

<P>

Yes, this does seem like too much work to add a test. <P>

At this point we don't need to add anything to setUp and tearDown. You could add information on the test to the Help. <P>

There's nothing magical about TDD when it comes to creating good tests. See What is Tested? at Unit Testing and Test Driven Development. Testing skills are a requirement. <P>

The inital RadioTest::SelectStationTest looks like:

LnStatus 
RadioTest::SelectStationTest()
{
	LMSG("RadioTest:SelectStationTest");

	bool make_the_test_fail= true;

	TEST_FAIL_IF(make_the_test_fail);

	return LN_OK;

}// RadioTest


The RadioTestSuite constructor looks like:

RadioTestSuite::RadioTestSuite(RWCString suiteName)
	:	TestSuite(suiteName)
{
	// Add tests here so the help will be available at the vxworks
	// shell.
	//
	addTest(new RadioTest("SelectStationTest", this));

}// ExampleTestSuite


RadioTest::runTest looks like:

LnStatus 
RadioTest::runTest()
{
	D1(&Test::sDebugModule, "RadioTest:runTest name=" << name());

    CPP_UNIT_TESTCASE_DISPATCH(SelectStationTest);
   
	SET_XCEPTION_IF(true, 
		LN_FAIL, "ASSERT", "", "", 0, 
		"runTest failure. The test specified does not exist. Test=" << name());

	return LN_OK;

}// runTest


Run The Initial Test

Now compile and run the test. The test should run and fail. A lot of things can go wrong in the process of adding a test so just getting the test to work is a nice first step.


Make the Test Pass

Now we start our first "productive" bit of coding :-) We need to make the test pass which means we need implement some code. <P>

Create Radio Class

Clearly we need to have something to select a station on. That would be a radio. So first let's make an empty Radio class and add it to the project. Follow the process at Add a New Class Under Test at CppUnitProgrammersGuide.

We make a Radio.h and Radio.cpp files in the Radio library directory. The Radio class is not copyable so it is derived from Notcopyable. We want to have debug in Radio so we pass a Module* to the constructor. There are no virtual methods so the destructor is not virtual. RadioTest is made a friend of Radio. <P>

The Radio class looks like:

 #ifndef _Radio_h_
 #define _Radio_h_

 #include "Util/Noncopyable.h"   // ISA
 #include "Util/Module.h"		// USES

class RadioTest;	// forward reference

/**
 * Radio represents the model of a radio for use in a
 * model-view-controller type system. It is the software
 * representation of a Radio. It is not meant to act as
 * a physical radio.
 */
class Radio : Noncopyable
{
public:
// LIFE CYCLE

	/**
	 * Create a new Radio.
	 */
	Radio(Module* pModule= 0);

	/**
	 * Delete this object.
	 */
	Radio();

private:
	friend RadioTest;

	/**
	 * Module associated with this object.
	 */
	Module*	mpModule;
};


Add RadioStartTest Test

RadioStartTest is added before SelectStationTest in the RadioTestSuite constructor because we want to verify Radio construction works before running later tests. <P>

RadioStartTest is added using the same process as for creating any other test. In this test we want to make sure the Module* is set correctly on construction.

LnStatus 
RadioTest::RadioStartTest()
{
	LMSG("RadioTest:RadioStartTest:");

	{
		Radio radio;
		TEST_FAIL_IF(radio.mpModule != 0);
	}

	{
		Radio radio(&Test::sDebugModule);
		TEST_FAIL_IF(radio.mpModule != &Test::sDebugModule);
	}

	return LN_OK;

}// RadioStartTest


For the test we use the debug module provided by the Test class. <P>

Run the test. My test fails because the compiler didn't find Radio.h. I didn't add an include statement for it. Remember, all library include paths should include the directory path, just not the class file name, so Jam can calculate dependencies correctly. For example:

#include "Radio/Radio.h"	// class tested

So, i added the include statement for Radio.h and ran the test again. It still fails. The include environment paths for creating this example aren't correct so i added the correct search paths. Now it passes. <P>


Add Radio Object to SelectStationTest

Let's declare a Radio object on the stack in the SelectStationTest test. At this point the code looks like:

LnStatus 
RadioTest::SelectStationTest()
{
	LMSG("RadioTest:SelectStationTest");

	Radio radio(&Test::sDebugModule);

	return LN_OK;

}// SelectStationTest


Run the test. It should pass as the Radio construction test has passed and we haven't changed anything else. We move in small steps and every step is verified.


Add SelectStation Method to Radio

Next, let's implement the station selection behaviour by adding a SelectStation method to Radio. <P>

For now i'll just represent a station as a double because that allows the representation of stations like 88.5 and 810. I don't know anything about station ranges other than a negative number don't make sense. <P>


Run the test. It passes. It should pass because we aren't doing anything yet :-) But at least we know the method has been added successfully and no compiler errors that will cause later failures. <P>

Next, add a call in the test to SelectStation. When selecting arguments your tests follow good testing practices. This is the first of many tests so we pick a simple value. <P>

SelectStationTest looks like:

LnStatus 
RadioTest::SelectStationTest()
{
	LMSG("RadioTest:SelectStationTest");

	Radio radio(&Test::sDebugModule);

	radio.SelectStation(100.1);

	return LN_OK;

}// SelectStationTest


Run the test. It should pass because we still haven't done much. <P>

We can't verify the station has been set because the SelectStation doesn't save the value. Let's add a mStation attribute to Radio to store the station. Make sure to initialize the mStation to a known value (0) in the constructor. If the new argument were allocating memory we would immediately add a statement to delete it in the destructor. Handling initialization and destruction immediately will help prevent errors. Remember to add constructor initializers in the same order as they are declared in the class or the compiler will issue a warning. <P>

A test must be added to RadioStartTest to test that the initial value for mStation is correct. <P>

To verify the station selection we can access mStation directly because RadioTest is a friend of Radio. Or we can add an accessor to get the value. We don't have a use case yet for an accessor, but getting the station will likely be a common operation so we add a GetStation() method to Radio. In this scenario we are allowing testing to be a generator of use cases. <P>

We don't add an accessor for the Module pointer because there's no reason for any user of the class to know the Module associated with Radio. <P>


Run the test. RadioStartTest should pass. <P>


Now using the new accessor let's add a test to verify the setting of the station to SelectStationTest. <P>

Run the test. SelectStationTest should pass. <P>

I added the output operator to Radio so i can see what's going on through debug output. <P>

The Radio class looks like:

class Radio : Noncopyable
{
public:
// LIFE CYCLE

	/**
	 * Create a new Radio.
	 */
	Radio(Module* pModule= 0);

	/**
	 * Delete this object.
	 */
	Radio();


// OPERATORS

   /**
    * Output object to a given stream.
    *
    * @param s The output stream to write to.
    * @param o The object to print.
    *
    * @return The stream written to.
    */
   friend ostream&         operator<< (ostream& s, const Radio& o);


// OPERATIONS

	/**
	 * Select a station.
	 */
	void		SelectStation(double station);

	/**
	 * Get the current station selection.
	 */
	double		GetStation(void) const;

private:
	friend RadioTest;

	/**
	 * Module associated with this object.
	 */
	Module*	mpModule;


	/**
	 * The current station selection.
	 */
	double	mStation;
};


The new SelectStationTest looks like:

LnStatus 
RadioTest::SelectStationTest()
{
	LMSG("RadioTest:SelectStationTest:");

	Radio radio(&Test::sDebugModule);

	radio.SelectStation(100.1);
	TEST_FAIL_IF(radio.GetStation() != 100.1);

	LMSG("radio=" << radio);

	return LN_OK;

}// SelectStationTest


The new RadioStartTest looks like:

LnStatus 
RadioTest::RadioStartTest()
{
	LMSG("RadioTest:SelectStationTest:");

	// We are using a block so we can test separate radio instances
	// without resorting to different names for each radio object.

	{
		// We are testing that the constructor is constructing the
		// Radio object correctly. The module and the station should
		// both be initialized to 0.
		//
		Radio radio;
		TEST_FAIL_IF(radio.mpModule != 0);
		TEST_FAIL_IF(radio.GetStation() != 0);
	}


	{
		// We are testing that the constructor taking a Module* is actuall
		// setting the module correctly. Some people might not make this
		// test because it is obvious from inspection.
		//
		Radio radio(&Test::sDebugModule);
		TEST_FAIL_IF(radio.mpModule != &Test::sDebugModule);
	}

	return LN_OK;

}// RadioStartTest

Let's Consider Error Checking in SelectSection

Should SelectStation use DesignByContract or an exception to verify the station argument? <P>

Radio should never have an invalid value because the GUI should have checked so DBC should be used. In this case i'd argue the error could only be made in a situation where something is seriously wrong so DBC should be used. <P>


The New SelectStation

For example:

void 
Radio::SelectStation(double station)
{
	REQUIRE(station > 0);

	mStation= station;

}// SelectStation


Discussion of the First Use Case

At first blush it seems a lot work was done for not much of a result. A lot of text has certainly been written :-) We got one class started and one method tested. We know the SelectStation method probably isn't the final version. And there are a lot of open issues yet to be resolved. <P>

Most of the work is in the initial setup. Once the setup is done adding new tests and TestCases goes quickly. <P>

The example uses small steps on purpose. In real-time the small steps didn't take that long, probably less than 10 minutes. The text looks longer than the process was. Each new test will take much less time. You could have done more work at a time to speed things up. The downside is with larger steps you may have introduced an error that smaller steps would have caught sooner. Everything is a tradeoff. In the long run shorter steps will save time. <P>


Let's Implement the Second Use Case

The completed second use case can be found under Use Case Two. For detailed how-to instructions see Let's Implement the First Use Case. Future uses cases will leave out much of the detail of how stuff is done. <P>

The second use case is Change the volume. <P>

Let's add stub RadioTest::ChangeVolumeTest. Run the test. It should fail. Let's make it pass by implementing Radio::SelectVolume. <P>


SelectVolume needs to be added to Radio.h and Radio.cpp. <P>

First thing that comes to mind in SelectVolume is how is the volume represented? Ask the customer. The customer says volume has the range 0-10. An advanced product may later have a range of 0-11. <P>

An enum seems a simple first take to represent volume:

	typedef enum VolumeType
	{
		MIN_VOLUME = 0,
		MAX_VOLUME = 10
	};


Run the tests to test for a compiler error. <P>


Let's add a volume attribute to Radio to store the volume. Remember the constructor must initialize the new attribute and RadioStartTest must be updated to test inialization of the volume attribute. <P>

Run the tests to test for a compiler error. <P>

We are in a position now to implement SelectVolume:


void 
Radio::SelectVolume(VolumeType volume)
{
	REQUIRE(volume >= MIN_VOLUME);
	REQUIRE(volume <= MAX_VOLUME);

	mVolume= volume;

}// SelectVolume


Design-by-contract is used to validate the volume attributes
because we are assuming the higher levels will be passing
in valid data.
<P>

We'll need an accessor to get the volume so we can test setting 
the volume. We also peeked ahead and we know the getting the volume 
is a uses case, so we can feel free to add <B>GetVolume</B>.
<P>

<B>The Radio class looks like</B>:


<PRE>
class Radio : Noncopyable
{
public:
// TYPES

	typedef enum VolumeType
	{
		MIN_VOLUME = 0,
		MAX_VOLUME = 10
	};

// LIFE CYCLE

	/**
	 * Create a new Radio.
	 */
	Radio(Module* pModule= 0);

	/**
	 * Delete this object.
	 */
	Radio();


// OPERATORS

   /**
    * Output object to a given stream.
    *
    * @param s The output stream to write to.
    * @param o The object to print.
    *
    * @return The stream written to.
    */
   friend ostream&         operator<< (ostream& s, const Radio& o);

// OPERATIONS

	/**
	 * Select a station.
	 */
	void		SelectStation(double station);

	/**
	 * Get the current station selection.
	 */
	double		GetStation(void) const;

	/**
	 * Select a volume.
	 */
	void		SelectVolume(VolumeType volume);

	/**
	 * Get the current volume.
	 */
	VolumeType	GetVolume(void) const;

private:
	friend RadioTest;

	/**
	 * Module associated with this object.
	 */
	Module*		mpModule;

	/**
	 * The current station selection.
	 */
	double		mStation;

	/**
	 * The current volume.
	 */
	VolumeType	mVolume;
};

Now, we can implement RadioStartTest. One ugly downside of the enum based implementation of volume is that you can't just use a raw number like 0 to set the volume as there is no autoconversion, there must be a cast to Radio::VolumeType. <P>

RadioTest::RadioStartTest looks like:

LnStatus 
RadioTest::RadioStartTest()
{
	LMSG("RadioTest:RadioStartTest:");

	{
		Radio radio;
		TEST_FAIL_IF(radio.mpModule != 0);
		TEST_FAIL_IF(radio.GetStation() != 0);
		TEST_FAIL_IF(radio.GetVolume() != Radio::MIN_VOLUME);

	}

	{
		Radio radio(&Test::sDebugModule);
		TEST_FAIL_IF(radio.mpModule != &Test::sDebugModule);
	}

	return LN_OK;

}// RadioStartTest

Now we can implement the ChangeVolumeTest. <P>

RadioTest::ChangeVolumeTest looks like:

LnStatus 
RadioTest::ChangeVolumeTest()
{
	LMSG("RadioTest:ChangeVolumeTest:");

	Radio radio(&Test::sDebugModule);

	radio.SelectVolume((Radio::VolumeType) 0);
	TEST_FAIL_IF(radio.GetVolume() != 0);

	radio.SelectVolume((Radio::VolumeType) 10);
	TEST_FAIL_IF(radio.GetVolume() != 10);

	LMSG("radio=" << radio);

	return LN_OK;

}// ChangeVolumeTest

Run the tests. They should all pass. <P>


The RadioTestSuite contructor looks like:


RadioTestSuite::RadioTestSuite(RWCString suiteName)
	:	TestSuite(suiteName)
{
	// Add tests here so the help will be available at the vxworks
	// shell.
	//
	addTest(new RadioTest("ChangeVolumeTest", this));
	addTest(new RadioTest("RadioStartTest", this));
	addTest(new RadioTest("SelectStationTest", this));

}// RadioTestSuite


The RadioTest::runTest looks like:


LnStatus 
RadioTest::runTest()
{
	D1(&Test::sDebugModule, "RadioTest:runTest name=" << name());

    CPP_UNIT_TESTCASE_DISPATCH(ChangeVolumeTest);
    CPP_UNIT_TESTCASE_DISPATCH(RadioStartTest);
    CPP_UNIT_TESTCASE_DISPATCH(SelectStationTest);
   
	SET_XCEPTION_IF(true, 
		LN_FAIL, "ASSERT", "", "", 0, 
		"runTest failure. The test specified does not exist. Test=" << name());

	return LN_OK;

}// runTest


Discussion of the Second Use Case

The whole process took about 4 minutes. There were several compiler errors that were quickly fixed. The tests passed because we implemented the feature in small steps. <P>

Use Cases Three and Four

These uses cases have already been satisfied by accessors.

  1. See what the current volume is.
  2. See what the current station is.

Let's Implement the Fith Use Case

The completed second use case can be found under Use Case Five.

The fith use case is Scan for the next station that can be heard.

The first question that comes to mind is if scanning is to be implemented in this software version of the radio or will it be implemented in the physical device? If so we can just delegate scanning. Asking the customer they say it must be done in software. The device can only tune to a specific frequency.

Scanning means iterating over every station to find one with a strong enough signal to be heard. Station is a double so iterating over the range of a double is clearly impracticle. So our current definition of station doesn't meet the new requirements of this use case. We must change our code. We knew the idea of station was too simple and would later be changed, but we did the simplest thing that could possibly work to fullfill the first use case. As we'll see it's not a big deal to change the code so it's not a problem to have made the initial choice of double. <P>

Since our customer is not a radio expert i went to the web to get a better idea of what is a station. Doing a search on radio in google yielded several useful results: How Radio Works, How the Radio Spectrum Works, Frequency vs. Wavelength, Catch A Wave: Radio Waves and How they Work.

FM occupies frequency range 88 megahertz (millions of cycles per second) to 108 megahertz. Inside that band, each station occupies a 200-kilohertz slice, and all of the slices start on odd number boundaries. So there can be a station at 88.1 megahertz, 88.3 megahertz, 88.5, etc. Interestingly, in Europe, the FM stations are spaced 100 kilohertz apart instead of 200 kilohertz apart, and they can end on even or odd numbers. <P>

AM occupies frequency range 530 kHz to 1700 kHz. Each AM station is allocated a frequency band of 10kHz in which to transmit its signal. The frequency band is centered around the carrier frequency of the station. A station at 610 on your dial transmits at a carrier frequency of 610kHz and the signal occupies the range of 605kHz to 615kHz. <P>

Logic for selecting the next station depends on the frequency. Now, we could add all the frequency selection logic to the Radio class. Should we? What it would look like? <P>

Radio would be taken over by station scanning implementation details. There would be a lot of methods and attributes related to selecting stations which would make make Radio harder to understand and harder to test. Station selection is complicated enough that it needs its own class. Radio would then HASA the class and delegate to it. For example purposes i considered showing this whole process. But it would be a lot of work. Hopefully you can see why Station and Radio should be separated into their own classes. <P>

Implementing Station

We'll call the new class Station. A nice simple noun. We could add new Station tests to RadioTest, but that would complicate RadioTest unecessarily and make harder for developers to work on code simultaneously.

Create Station.{h,cpp} and StationTest.{h,cpp}

Let's add a new StationTest test case just for testing the Station object. Copy RadioTest to StationTest is an easy way to get started. Similarily we'll copy Radio to Station. Removing the Volume code from Station and StationTest gives a reasonable first implementation. Add the new classes to your project. <P>


Add the new tests StationTest::StationStartTest and StationTest::SelectStationTest to RadioTestSuite. Radio::SelectStationTest still exists because it is still a requirement for Radio. <P>


We are Refactoring. Our goal should be to get the Station class started and change Radio to work immediately with the new Station class so that Radio passes all its tests. We want to minimize our window of broken code. We don't want to completely implement Station if it means keeping Radio broken. <P>

Run your tests. All the Radio and Station tests should pass. All the Station tests are new and we didn't change the Radio class yet to use the new Station class. That's our next step. <P>

Make Station Changes

Change Radio to use the Station object instead of the double currently being used. Return const Station& from GetStation instead of a double. Declare mStation as Station instead of a double. <P>

There's a decision to be made on SelectStation. Should we still use a double to select a station or should we require a Station object be passed in? From a type safety point of view we should require a Station object. That would allow all lower level code to use a true Station abstraction that is known to be valid. Passing around a double value means we never know if the double value is valid and it would have to be converted to a Station object before use. <P>

So we must change Radio::SelectStation to take a const Station& argument. This means Station must support a copy constructor so we can assign the station to Radio::mStation. Station then should not derive from Noncopyable anymore. For convenience we also make the Station constructor take a frequency. StationStartTest must be changed to test the new constructors. To make testing easier we also add operators == and !=. Tests for the new operators must also be added to StationStartTest. <P>

Remember, you can do all these changes in small steps or in bigger steps. I first made the changes to Radio. This caused RadioTest to fail because Radio::GetStation returns an object now, not a double, which caused the != compares to not compile. This prompted the adding of the != and == operators to Station. Then i made the changes to Station and added the tests in one step. <P>

Run tests. The Radio tests should pass and the Station tests should pass. <P>


What did we just do? <P>

We refactored out a new class, added a bunch of tests for the new class, and made minor changes to Radio to make sure Radio still functioned properly. Radio was never very far out of working because we made the changes in a small iterations and we had tests to verify its functionality. The compiler was also helpful in telling us that the Radio::SelectStation no longer worked. <P>

We are refactoring which means we are trying to improve our code. Should the Station object use the name Station for attibute and accessor names? It's confusing in the context of the Station class and we know now frequency is probably a better name. So let's change mStation to mFrequency and SelectStation to SetFrequency and GetStation to GetFrequency. This should be clearer. Running the compiler will clearly tell all the places that need fixing. <P>

Run tests. All the test should pass. We made the code clearer (i think) with relatively little effort and knowing we didn't break anything. <P>

The Station class looks like:

class Station
{
public:

// LIFE CYCLE

	/**
	 * Copy constructor.
	 *
	 * @param rCopyFrom The station to copy. MEMORY: COPY.
	 */
	Station(const Station& rCopyFrom);

	/**
	 * Create a new Station.
	 *
	 * @param pModule Module to associate with this object.
	 *  MEMORY: PBORROWED.
	 */
	Station(Module* pModule= 0);

	/**
	 * Delete this object.
	 */
	Station();

// OPERATORS

   /**
    * Output object to a given stream.
    *
    * @param s The output stream to write to.
    * @param o The object to print.
    *
    * @return The stream written to.
    */
   friend ostream&         operator<< (ostream& s, const Station& o);


   /**
    * Are the objects equal by value;
    */
   bool         operator== (const Station& rCmpTo) const;


   /**
    * Are the objects not equal by value;
    */
   bool         operator!= (const Station& rCmpTo) const;


// OPERATIONS

	/**
	 * Select a station.
	 */
	void		SetFrequency(double station);


	/**
	 * Get the current station selection.
	 */
	double		GetFrequency(void) const;

private:
	friend StationTest;

	/**
	 * Module associated with this object.
	 */
	Module*		mpModule;


	/**
	 * The current station selection.
	 */
	double		mFrequency;
};

Continue with Scan Implementation

Recall that we are still trying to implement the scan functionality so we'll continue with that development effort. <P>

Let's add the method Station::ScanForNextAvailableStation and set up a the stub test StationTest::ScanForNextAvailableStationTest for it. Using a long descriptive name like ScanForNextAvailableStation is a form of documentation and make the code clearer. Run the tests. <P>

Scanning means moving from one valid frequency to the next and testing it to see if we can hear anything on that frequency. In a real system we would sample around the official frequency as well, but we won't just to make it simpler. <P>

At first, it appeared Station needed the idea of AM or FM so scanning could wrap around and stay in the AM or FM band. But wouldn't it be better to scan from AM through FM if the idea is to find something to listen to? Who cares if it's AM or FM? Current radios only seem to scan within AM or FM, but that's probably just an implementation limitation. We can do better :-) The customer was asked for clarification and they agreed to this definition of scanning. <P>

Adding an AM or FM option to Station may not be bad idea, and we may end up doing it in the future, but we have no use case for it now so we don't do it. When that time comes it might force other options like creating a derived class each for AM and FM. For now we are keeping Station as is. <P>

Change to uint32, add IsValidFrequency, add constants enum

The rules for finding the next frequency depend on the frequency as AM am FM have different rules. That means the frequency must be a valid AM or FM range. Currently the constructor takes a double which can be any value. This is wrong. Instead let's represent all frequencies in kHz as AM and FM can both be expressed in kHz. This means we should change the double to a uint32. <P>

We'll setup an enum in Station with AM and FM constants. Our current tests will break because they probably pass in numbers that aren't valid frequencies. Let's just make the uint32 change and then see what happens. <P>

When changing SetFrequency it's clear determining the validity of the frequency is harder to calculate so let's create an IsFrequencyValid method for this purpose. We'll add verification tests for this method in StationTest::StationStartTest. <P>

With the change from double to uint32 there will be a lot of compiler failures and test failures. Just fix them, it's all pretty mechanical. <P>

See the changes in directory UseCaseFive.1. <P>

The Station class looks like:

class Station
{
public:
// TYPES

	/**
	 * Configuration constants relating to station frequencies.
	 */
	enum
	{
		AM_START_KHZ			= 530,
		AM_STOP_KHZ				= 1700,
		AM_STATION_OFFSET_KHZ	= 10,

		FM_START_KHZ			= 88100,	// starts on odd boundry
		FM_STOP_KHZ				= 108000,
		FM_STATION_OFFSET_KHZ	= 200,
	};

// LIFE CYCLE

	/**
	 * Copy constructor.
	 *
	 * @param rCopyFrom The station to copy. MEMORY: COPY.
	 */
	Station(const Station& rCopyFrom);

	/**
	 * Create a new Station.
	 *
	 * @param frequency The frequency of the station.
	 * @param pModule Module to associate with this object.
	 *  MEMORY: PBORROWED.
	 */
	Station(uint32 frequency= AM_START_KHZ, Module* pModule= 0);

	/**
	 * Delete this object.
	 */
	Station();

// OPERATORS

   /**
    * Output object to a given stream.
    *
    * @param s The output stream to write to.
    * @param o The object to print.
    *
    * @return The stream written to.
    */
   friend ostream&         operator<< (ostream& s, const Station& o);

   /**
    * Are the objects equal by value;
    */
   bool         operator== (const Station& rCmpTo) const;

   /**
    * Are the objects not equal by value;
    */
   bool         operator!= (const Station& rCmpTo) const;

// OPERATIONS

	/**
	 * Select a station.
	 */
	void		SetFrequency(uint32 station);

	/**
	 * Get the current station selection.
	 */
	uint32		GetFrequency(void) const;

	/**
	 * Scan for the next station.
	 */
	void		ScanForNextAvailableStation(void);

	/**
	 * Is the frequency valid for this station.
	 */
	LnStatus	IsFrequencyValid(uint32 frequency) const;

private:
	friend StationTest;

	/**
	 * Module associated with this object.
	 */
	Module*		mpModule;

	/**
	 * The current station selection.
	 */
	uint32		mFrequency;
};


Implement ScanForNextAvailableStation

The first take is something like:

void		
Station::ScanForNextAvailableStation(void)
{
 	if (mFrequency >= AM_START_KHZ && mFrequency <= AM_STOP_KHZ)
	{
		mFrequency+= AM_STATION_OFFSET_KHZ;

		if (mFrequency >= AM_STOP_KHZ)
			mFrequency= FM_START_KHZ;
	}
	else if (mFequency >= FM_START_KHZ && mFrequency <= FM_STOP_KHZ)
	{
		mFrequency+= FM_STATION_OFFSET_KHZ;

		if (mFrequency >= FM_STOP_KHZ)
			mFrequency= AM_START_KHZ;
	}
	else
	{
		REQUIRE(0); // should never get here
	}

}// ScanForNextAvailableStation


The logic keeps us incrementing in an infinite loop. Do want want to know if we've wrapped? Do we want to stop after a wrap? Should we keep count of the wraps? There's no current use case for doing anything but infinite looping so we won't add in any extra features. <P>

It's also clear that we move forward only when the method is called. There's no loop yet. What is our terminating condition? When we find a station that has something on it. How do we know that? Currently there is no mechanism. <P>

So who should be responsible for knowing if a station is enough signal that we think a person should listen to it? Radio doesn't know about the "real" radio. It could though. But there's no compelling reason it should. Station could. That would require station to have been created by a factory interface that know what kind of thing the station belonged to. Which may be a good idea for a number of reasons, but it's not absolutely necessary right now so we won't do it yet. <P>

Another possibility is for the client code to pass in a ScanApply to ScanForNextAvailableStation. The apply method is given every frequency selection and it can do whatever it wants with the frequency. It could test the frequency for signal and stop if it finds one. It could track wraps and stop if it wanted. And because ScanApply would be an abstract base clase we could derive a class from ScanApply to test the scan feature. <P>

Let's do the ScanApply approach. It decouples the different layers and allows applications a great deal of freedom. It also keeps Station and Radio reusable without resorting to more complicated construction patterns. <P>

You may be tempted to have Radio know how to switch between stations. But the downside of that approach is that Radio has to have initimate knowledge of frequencies and any other Station internals, which would make Radio and Station strongly coupled. <P>


ScanApply looks like:


/**
 * ScanApply is applied to every valid station frequency when
 * passed to ScanForNextAvailableStation. The Apply is in an
 * infinite loop so the implementation must have a mechanism
 * for terminating the loop.
 */
class ScanApply
{
public:
	/**
	 * Construct new object.
	 */
	ScanApply();

	/**
	 * Destroy this object.
	 */
	virtual ScanApply();

	/**
	 * Apply application logic to a Station.
	 *
	 * @param pStation The station to apply to.
	 * 
	 * @return true to continue with the apply loop,
	 *  flase to stop the apply loop.
	 */
	virtual bool	Apply(Station* pStation) = 0;
};


We won't create a separate TestCase for ScanApply because it doesn't do anything and will be tested via derived classes. <P>

We need to change Station::ScanForNextAvailableStation to take the apply class and use it to drive the search loop. The new method looks like:


void		
Station::ScanForNextAvailableStation(ScanApply& rApply)
{
	bool is_continue= true;

	while (is_continue) 
	{
		// Move to the next frequency first so we don't get
		// stuck on the first frequency if it happens to have
		// a signal.
		//
 		if (mFrequency >= AM_START_KHZ && mFrequency <= AM_STOP_KHZ)
		{
			mFrequency+= AM_STATION_OFFSET_KHZ;

			if (mFrequency >= AM_STOP_KHZ)
				mFrequency= FM_START_KHZ;
		}
		else if (mFrequency >= FM_START_KHZ && mFrequency <= FM_STOP_KHZ)
		{
			mFrequency+= FM_STATION_OFFSET_KHZ;

			if (mFrequency >= FM_STOP_KHZ)
				mFrequency= AM_START_KHZ;
		}
		else
		{
			REQUIRE(0); // should never get here
		}

		is_continue= rApply.Apply(this);

	}// while the apply is still interested

}// ScanForNextAvailableStation


Refactor Out to MoveToNextFrequency

We haven't written any tests yet. This has all been done in one big chunk, which is probably a mistake. The reason i did it this way is that while coding i noticed several refactoring opportunites that i wanted to separate out and write tests for rather than writing tests for an unfactored ScanForNextAvailableStation. <P>

We should be thinking refactoring all the time. This method has a big chunk of code to determine the next frequency that could/should be pulled out into its own seperately testable method. <P>

Let's extract it out to MoveToNextFrequency:

void
Station::MoveToNextFrequency(void)
{
 	if (mFrequency >= AM_START_KHZ && mFrequency <= AM_STOP_KHZ)
	{
		mFrequency+= AM_STATION_OFFSET_KHZ;

		if (mFrequency >= AM_STOP_KHZ)
			mFrequency= FM_START_KHZ;
	}
	else if (mFrequency >= FM_START_KHZ && mFrequency <= FM_STOP_KHZ)
	{
		mFrequency+= FM_STATION_OFFSET_KHZ;

		if (mFrequency >= FM_STOP_KHZ)
			mFrequency= AM_START_KHZ;
	}
	else
	{
		REQUIRE(0); // should never get here
	}

}// MoveToNextFrequency

Which changes ScanForNextAvailableStation:


void		
Station::ScanForNextAvailableStation(ScanApply& rApply)
{
	bool is_continue= true;

	while (is_continue) 
	{
		// Move to the next frequency first so we don't get stuck on the first 
		// frequency if it happens to have a signal.
		//
 		MoveToNextFrequency();

		is_continue= rApply.Apply(this);

	}// while the apply is still interested

}// ScanForNextAvailableStation


Refactor Out IsAmFrequency and IsFmFrequency

Before we test MoveToNextFrequency let's look for more refactoring opportunities. Station::IsFrequencyValid and Station::MoveToNextFrequency have very similar code to test for AM and FM frequencies. We should probably refactor them out and test it separately. So we add Station::IsAmFrequency(uint32 frequency) const and Station::IsFmFrequency(uint32 frequency) const. Let's add tests to StationTest::StationStartTest. Yes, StationStartTest has become a sort of a dumping ground for miscellaneous short tests. Note that the test for IsFrequencyValid can be shortened because the new tests are take over much of the tests.


Test MoveToNextFrequency

Not that we've finished our little refactoring session it's time to test MoveToNextFrequency. Let's add StationTest::MoveToNextFrequencyTest. Remember to place MoveToNextFrequencyTest before ScanForNextAvailableStationTest in RadioTestSuite as the scan method uses MoveToNextFrequency. <P>

Found a bug with this test:

	station.SetFrequency(Station::AM_STOP_KHZ - Station::AM_STATION_OFFSET_KHZ);
	station.MoveToNextFrequency();
	TEST_FAIL_IF(station.GetFrequency() != Station::AM_STOP_KHZ);


I expected AM_STOP_KHZ to be a valid frequency, but the >= in this logic skipped passed it:

	if (mFrequency >= AM_STOP_KHZ)
			mFrequency= FM_START_KHZ;

After changing the test to > the test passed again. <P>

We should test at different starting frequencies. If we start with a odd frequency like 657 the tests will fail because we won't move to the next valid frequency. The solution to this is that only correct frequencies should every be passed to Station. IsAmFrequency and IsFmFrequency should be changed to make sure the frequency is not an odd frequency like 657. So let's go change the code and update tests. The new validation macros are:


bool
Station::IsAmFrequency(uint32 frequency) const
{
	if (frequency < AM_START_KHZ || frequency > AM_STOP_KHZ)
		return false;

	if (((frequency - AM_START_KHZ) % AM_STATION_OFFSET_KHZ) != 0)
		return false;

	return true;

}// IsAmFrequency

bool
Station::IsFmFrequency(uint32 frequency) const
{
	if (frequency < FM_START_KHZ || frequency > FM_STOP_KHZ)
		return false;

	if (((frequency - FM_START_KHZ) % FM_STATION_OFFSET_KHZ) != 0)
		return false;

	return true;

}// IsFmFrequency


This causes the test to fail because FM_STOP_KHZ is 108000 which is not a valid a frequency. Changing it to 107900 makes it a valid value. A number of other tests fail because the frequencies where specified like AM_START_KHZ + 5 which are no longer valid. These tests were pretty easy to fix. <P>


The complete MoveToNextFrequencyTest test looks like:


LnStatus 
StationTest::MoveToNextFrequencyTest()
{
	LMSG("StationTest:MoveToNextFrequencyTest:");

	// An exaustive test is not practical so we will test the
	// initial frequency sequency and the wrap between AM and FM
	// and the wrap back from FM to AM.
	Station station;

	station.MoveToNextFrequency();
	TEST_FAIL_IF(station.GetFrequency() != 
		Station::AM_START_KHZ + Station::AM_STATION_OFFSET_KHZ);

	station.MoveToNextFrequency();
	TEST_FAIL_IF(station.GetFrequency() != 
		Station::AM_START_KHZ + (Station::AM_STATION_OFFSET_KHZ*2));

	station.SetFrequency(Station::AM_STOP_KHZ - Station::AM_STATION_OFFSET_KHZ);
	station.MoveToNextFrequency();
	TEST_FAIL_IF(station.GetFrequency() != Station::AM_STOP_KHZ);

	station.MoveToNextFrequency();
	TEST_FAIL_IF(station.GetFrequency() != Station::FM_START_KHZ);

	station.MoveToNextFrequency();
	TEST_FAIL_IF(station.GetFrequency() != 
		Station::FM_START_KHZ + Station::FM_STATION_OFFSET_KHZ);

	station.SetFrequency(Station::FM_STOP_KHZ - Station::FM_STATION_OFFSET_KHZ);
	station.MoveToNextFrequency();
	TEST_FAIL_IF(station.GetFrequency() != Station::FM_STOP_KHZ);

	station.MoveToNextFrequency();
	TEST_FAIL_IF(station.GetFrequency() != Station::AM_START_KHZ);

	return LN_OK;

}// MoveToNextFrequencyTest


Test Station::ScanForNextAvailableStationTest

Given MoveToNextFrequencyTest is now working we can test ScanForNextAvailableStation. We don't have to test frequency iteration because it has already been tested. We need to test that the Apply method gets called properly and that we can stop the apply loop. <P>

To implement the test let's create a TestScanApply class. We'll make it able to stop when it hits a particular frequency and count the number of times Apply has been called. This should be sufficient for us to know it works. <P>

The TestScanApply class looks like:

class TestScanApply : public ScanApply
{
public:
	/**
	 * @param rStopStation The station when reached that causes
	 *  the apply loop to stop.
	 */
	TestScanApply(const Station& rStopStation);

	virtual TestScanApply();

	virtual bool	Apply(Station* pStation);

	uint32			mApplyCalledCount;
	Station			mStopStation;
};


The test looks like:

LnStatus	
StationTest::ScanForNextAvailableStationTest(void)
{
	LMSG("StationTest:ScanForNextAvailableStationTest:");

	// Pick a station to stop at.
	//
	Station stop_station(Station::AM_START_KHZ + (3*Station::AM_STATION_OFFSET_KHZ));

	Station station;

	TestScanApply apply(stop_station);

	station.ScanForNextAvailableStation(apply);

	TEST_FAIL_IF(apply.mApplyCalledCount != 3);

	return LN_OK;

}// ScanForNextAvailableStationTest


See the resulting code in UseCaseFive.


Implement Radio::ScanForNextAvailableStation

We can scan from the Station object but we still can't scan from the Radio object. So we'll add Radio::ScanForNextAvailableStation and RadioTest::ScanForNextAvailableStationTest. We'll steal the corresponding Station test.


Discussion of Use Case 5

We started out implementing a scan feature for a particular use case. It resulted in quite a bit of change with the addition of new classes and changes in existing classes. Even though there seems like a lot of text in this document, the process actually wen't pretty fast because the changes were made in the flow of programming. We made good progress while knowing our code was working and that the code was meeting our requirements.


Let's Implement the Sixth Use Case

The completed second use case can be found under Use Case Six. <P>

The sixth use case is Keep a list of favorite stations. <P>

First question that comes to mind is how should the user indicate which station is a favorite. Ask the customer. The customer say the only method that is needed is to mark the current station as a favorite. <P>

So let's add stub methods for Radio::AddCurrentStationToFavorites and RadioTest::AddCurrentStationToFavoritesTest. Run the test. <P>

Let's make the test pass by implementing Radio::AddCurrentStationToFavorites. A question comes to mind: save to where, using what key? Which is another way of asking how the favorite station is identified to the client. Ask the customer. The customer says there can be a maximum of 10 favorites identified by 0-9. <P>

So let's make AddCurrentStationToFavorites take a uint32 to specify the key. Run the test. This little step just verifies the code compiles. <P>

In AddCurrentStationToFavorites where do we store the favorite station? Should we fold the favorite feature into Radio or use a separate class? Hopefully by now we know to use a separate class. <P>

Let's create a new Favorites class and TestCase. It's easier to copy Radio and RadioTest to Favorites and FavoritesTest and remove what is unecessary. We know we'll need test FavoritesTest::FavoritesStartTest. Make sure to add that to RadioTestSuite. Run the test. You should have 10 passing tests. <P>

We can now implement AddCurrentStationToFavorites in terms of Favorites. First we need to add Favorites to Radio as attribute mFavorites. There's no reason to add accessors yet. Run the tests. <P>

Next, AddCurrentStationToFavorites simply needs to call Favorites::AddStation with the passed in index and the current station. As AddStation doesn't exist we'll need to add a stub for it. Create a stub FavoritesTest::AddStationTest and add it to RadioTestSuite. Run the tests. 10 tests should have passed and one failed. <P>

AddCurrentStationToFavorites now looks like:


void				
Radio::AddCurrentStationToFavorites(uint32 key)
{
	mFavorites.AddStation(key, GetStation());

}// AddCurrentStationToFavorites


Let's make FavoritesTest::AddStationTest pass by implementing Favorites::AddStation. We know the max number of favorites is 10 so we can use a fixed size array of stations. Let's add an enum for the max number of stations. We don't make the number of stations configurable in the constructor because there is no used case for it. It would unecessarily complicate the code for no reason. <P>

There's a question as what should be in the array? Should it be Station objects or something else? Let's just store the uint32 that is the station frequency is this is the simplest thing. We can recreate a station from that frequency so let's go ahead and just store frequency. We are adding the array of uint32 as attribute of Favorites. Initialize the array to know values immediately. 0 is fine value because it's not a valid station frequency. <P>

Favorites::AddStation looks like:


void					
Favorites::AddStation(uint32 key, const Station& rStation)
{
	REQUIRE(key < MAX_FAVORITES);

	mFavoritesList[[Key | key]]= rStation.GetFrequency();

}// AddStation


We are going to need a get by key accessor for the test. Should the accessor return a frequency or station? What should we do if the frequency is 0? 0 isn't a valid value for a Station so we can't return a Station in that scenario. Returning a frequency is a possibility. That way the caller can check for 0 to know if there was a favorite. Or we could set an exception if a station wasn't valid. Or we could just default all favorites to the first AM station. Using the default of the first AM situation makes everything simpler and still yields acceptable results. So instead of using 0 as the initial value let's use Station::AM_START_KHZ. <P>


Favorites::Favorites looks like:

Favorites::Favorites(Module* pModule) 
:	mpModule(pModule)
{
	for (uint32 i= 0; i < MAX_FAVORITES; i++)
	{
		mFavoritesList[[I | i]]= Station::AM_START_KHZ;
	}

}// Favorites


Embedding an initialization loop in the constructor could be considered a code smell. Let's create an InitializeFavoritesList method instead. Let's add a test to FavoritesStartTest to verify that it's initialized. Run the test.


Favorites::Favorites(Module* pModule) 
:	mpModule(pModule)
{
	InitializeFavoritesList();

}// Favorites

void
Favorites::InitializeFavoritesList()
{
	for (uint32 i= 0; i < MAX_FAVORITES; i++)
	{
		mFavoritesList[[I | i]]= Station::AM_START_KHZ;
	}

}// InitializeFavoritesList

LnStatus 
FavoritesTest::FavoritesStartTest()
{
   LMSG("FavoritesTest:FavoritesStartTest:");

   {
      Favorites favorites;
      TEST_FAIL_IF(favorites.mpModule != 0);
      TEST_FAIL_IF(favorites.mFavoritesList[[[0 | [0]] != Station::AM_START_KHZ);
      TEST_FAIL_IF(favorites.mFavoritesList[[[Favorites::LAST_FAVORITES | [Favorites::LAST_FAVORITES]] != 
         Station::AM_START_KHZ);
    }

    return LN_OK;

}// FavoritesStartTest

Now we can add an accessor because we can always return a valid station.


Station				
Favorites::GetStation(uint32 key)
{
	REQUIRE(key < MAX_FAVORITES);

	return mFavorites[[Key | key]];

}// GetStation


Notice that a frequency is returned even though the return value is a Station. The magik here is that a temporary Station is being created that is assigned to the callers Station. <P>

Now we are in a good position to implement Favorites::AddStationTest.


LnStatus 
FavoritesTest::AddStationTest()
{
	LMSG("FavoritesTest:AddStationTest:");

	Favorites favorites;
	Station   station;

	favorites.AddStation(0, station);
	TEST_FAIL_IF(favorites.GetStation(0) != station);

	station.MoveToNextFrequency();
	favorites.AddStation(0, station);
	TEST_FAIL_IF(favorites.GetStation(0) != station);

	station.MoveToNextFrequency();
	favorites.AddStation(1, station);
	TEST_FAIL_IF(favorites.GetStation(0) == station);
	TEST_FAIL_IF(favorites.GetStation(1) != station);

	station.MoveToNextFrequency();
	favorites.AddStation(Favorites::LAST_FAVORITE, station);
	TEST_FAIL_IF(favorites.GetStation(0) == station);
	TEST_FAIL_IF(favorites.GetStation(Favorites::LAST_FAVORITE) != station);

	return LN_OK;

}// AddStationTest 

Notice how the use of a Station temporary as a return from GetStation and having != operator on Station makes the tests short and succinct. <P>

Now we can implement RadioTest::AddCurrentStationToFavoritesTest. We have tested the underlying ability of Favorites to set and get stations so we don't need to a thourough test. It turns out to implement the test we need a Radio::GetFavoriteStation(uint32 key) method so let's implement that. No need to add a test for it because it's tested in AddCurrentStationToFavoritesTest. We also need a way to move to the next station to faciliate testing so let's add Radio::MoveToNextStation. No need to test it because it's tested in Station and it will be tested in AddCurrentStationToFavoritesTest.


LnStatus			
RadioTest::AddCurrentStationToFavoritesTest()
{
   LMSG("RadioTest:AddCurrentStationToFavoritesTest:");

	Radio radio;

	radio.AddCurrentStationToFavorites(0);
	TEST_FAIL_IF(radio.GetStation() != radio.GetFavoriteStation(0));

	Station saved_station= radio.GetStation();

	radio.MoveToNextStation();
	radio.AddCurrentStationToFavorites(0);
	TEST_FAIL_IF(radio.GetStation() != radio.GetFavoriteStation(0));
	TEST_FAIL_IF(radio.GetStation() == saved_station);

	radio.AddCurrentStationToFavorites(1);
	TEST_FAIL_IF(radio.GetStation() != radio.GetFavoriteStation(1));
	TEST_FAIL_IF(radio.GetStation() == saved_station);


	radio.MoveToNextStation();
	radio.AddCurrentStationToFavorites(2);
	TEST_FAIL_IF(radio.GetStation() != radio.GetFavoriteStation(2));
	TEST_FAIL_IF(radio.GetStation() == saved_station);

	TEST_FAIL_IF(radio.GetFavoriteStation(1) == radio.GetFavoriteStation(2));

	return LN_OK;

}// AddCurrentStationToFavoritesTest


Station				
Radio::GetFavoriteStation(uint32 key)
{
	return mFavorites.GetStation(key);

}// GetFavoriteStation


void
Radio::MoveToNextStation(void)
{
	mStation.MoveToNextFrequency();

}// MoveToNextStation


Notice that the tests are short and clear when we add new accessors to our code. Tests are a legitimate source of use cases up to a certain point. All internals shouldn't be exposed just for testing. TestCases are friends of the classes under test so exposing internals isn't necessary. <P>

Also notice that the process of adding code driven by tests and use cases produced a clear result without a lot of extra baggage.


Last Refactoring Pass for Code Smells

Before we close up camp let's check for any serious Code Smells. <P>

Look at the Radio class. Does anything jump out at you? Why is the volume code in the Radio code? Wouldn't it be cleaner to refactor out the volume code into its own class so it has its own tests? This would give both the Radio and Volume class more cohesion and reduce coupling in the system. Both the classes and the tests become simpler and more understandable. <P>

You should know what to do by now. Give it a shot. Check my result in Volume. Your result may be different. <P>

Was the result worth the effort? <P>


Discussion of Test Driven Development vs Specification

How Different is the Design?

Is the TDD solution different than one where the design was derived from specification? I believe it is. The Radio problem is relatively simple so there isn't a lot of room for divergence, but there is a difference. <P>

Take a look at the classes produced: Radio, Favorites, Volume, Station, ScanApply. Look at the methods in each class. The classes have high cohesion and low coupling. The class names make it clear what is happening. The method names are simple to understand. The method names make it clear what is happening. Each class can be extended without breaking existing classes. New classes can be added easily. Not bad. <P>


Which Design is Better?

Is the TDD a better design? That's a hard question to answer. As i would have developed the specification based implementation using unit testing the reliability of either result should be the same. And TDD still would have been used to shape the specification based design. <P>

I know when thinking about all the use cases at once i was thinking of a much more complicated design for Station. The evolved design is simple and direct. But let's add a few more twists and see what would happen. What if we:

  1. Added satellite radio?
  2. Added internet radio?
  3. Changed to model a TV remote control?
  4. Changed to use station names (KNBR) instead of frequencies?
  5. Made it so Radio would work in europe too?

The specification based Station design probably would have handled these changes with less churn than the TDD derived design because it would have used a factory to create Station instances. Using a factory means different type specific Station classes could be used to handle satellite, AM, FM, and internet radio stations with european variants. The current Station implements both AM and FM bands, which is a bit ugly, but handling more types of stations and variants of stations clearly would require type specific classes. <P>

But, the factory approach would have been much more complicated for the actual problem at hand. There would have been more code, more tests, more classes, more possibility of error, etc. All for the promiss of easier change in the future. Yet, we don't exactly know what changes would be necessary in the future so our future-proof design may be wrong. So we have added risk to ur present and really haven't secured the future. So why do it? In some case where we do have a good idea of possible futures making current choices for future changes can be a big win. You'll have to decide the tradeoffs for your particular circumstance. <P>


Should We be Afraid of Change?

It's implied in the Which Design is Better? section that we are afraid of change. Why should we be afraid? The process of changing the code isn't a disaster as long as we do it in small steps and can run tests to have confidence that we are aren't screwing things up. <P>

This observation does not appply to architecture level frameworks in a large project, where it can be very painful to change code. But when developing code using the frameworks and even in developing the frameworks TDD is a viable strategy. <P>


Implications of Adding New Uses Cases?

It probably would change the priority of use cases and thus change what got implemented first. If there were several types of radio (satelite and internet) to be managed instead of just a normal radio, i would have definitely flagged that as a risk point and addressed it first. <P>


Which Approach Took Longer?

It seems like the code changed a lot during TDD. If we could have made the Station in its final form first the development period would have been shorter. Is it really possible to get to the final form of the code through specification? It may be possible, but it is highly unlikely, especially as problems become more complicated. It's likely a lot of extra code would have been added in the specification based solution as well. <P>

Again, the code changes in the TDD process didn't take very long and was very natural because it was driven by use cases in the normal flow of programming.

<P>

Personal tools