The heart of TDD

This article was originally published in VSJ, which is now part of Developer Fusion.
Initially, I thought that Test-Driven Development (TDD) was simply a way to automate the unit testing of my code. Unit testing, for me, is a bit like washing-up in that I tend to put it off until some sort of crisis forces me into action. Shamefully, I must admit that expediency rather than good practice usually governs my approach; dirty pans get hidden at the back of cupboards, specific test cases are ditched in favour of stepping through the code with a debugger, attempts to measure code coverage are abandoned – the results being too depressing! I hoped that TDD would provide me with the software equivalent of a washing-up machine, something that would prevent the build-up of unit testing and make sure it was always done perfectly.

It is now clear to me that Test-Driven Development is much more than just a means to guarantee the thorough unit testing of my code. In this, the first of two articles, I'll concentrate on explaining the nature of TDD, how it works, and how to build the sort of test framework you'll need. The next one will show how you can use TDD to build a simple GUI application and explain why it caused me to challenge some of my basic preconceptions about the nature of software development.

Language and Skill Level

There are over thirty different test frameworks available on the Internet (see Resources) so TDD is by no means targeted at a particular language. Indeed, one of its strengths is the universal way it can be applied. I've used my usual tools to build the examples in this article: C++ and Microsoft's Visual Studio. However, you should find no difficultly in building them with different tools, if required. You may even find that doing so improves your understanding. There is nothing hard about either TDD or this article.

TDD and Extreme Programming

Test-Driven Development is closely associated with Extreme Programming (XP), an agile software development process attributed to Kent Beck – see my previous article, in the February 2003 issue of VSJ. Although you can apply TDD to a project without adopting XP, or indeed any other form of process, they share a number of common themes, so you cannot really understand one without understanding the other.

XP proposes twelve core practices that promote the values of communication, simplicity, feedback and courage in a development team. XP is an iterative and incremental development process, such that releases are small and frequent, typically every couple of weeks. In such an environment it is essential to have continuous testing throughout the project, and this is what TDD helps to provide. However, Test-Driven Development also strongly influences the way you design and implement your code. The keys to this influence are the XP practices of refactoring and simple design (see Simple Design and Refactoring).

Courage, communication, feedback and simplicity are as much a part of TDD as they are XP. You need to adopt the same sort of mind-set to successfully practice them both. What you may discover, however, is that taking up Test-Driven Development as part of your Personal Software Process will cause you fewer political problems than declaring your project to be XP.

TDD – A Brief Overview

Let's start with a simple demonstration of how TDD can help us write some code that calculates the area of a rectangle and the length of its perimeter. What set of tests would prove the code needed to implement these requirements? You might be able to think of others, but two tests come instantly to mind: a test to validate the area of a rectangle, and a test to validate the perimeter of the same rectangle. We'll begin with the test that calculates the area of the rectangle.

We need to start-up Visual Studio 6.0, create a new empty project called "Quad" ( File | New – Win32 console application ), create a new file called Test.cpp (File | New – C++ Source File), and finally type-in the test case as shown below:

void main()
{
	Quad q;
	assert( 6 == q.area(2, 3));
}
When we build this code (Build | Build Quad.exe) there are number of compile errors as the code we are testing has not yet been written. Now, let's do the simplest and quickest things to fix these problems; write a class for 'Quad' and include a header for 'assert':
#include <assert.h>

class Quad {
public:
	Quad () {;}
	int area(int wd, int ht) { return 0; }
};
This time we can build the code without error and run the program (Build | Execute Quad.exe), but unfortunately it blows-up during execution with an error telling us that the assertion failed. Again, what's the simplest thing we can do to fix the problem? We can just hard code a return value of '6' for area(), rebuild it, and run the test again. This time it all works perfectly.

However, we're not finished yet as there is some duplication; '6' appears both in the test data and in the code data. Let's "refactor" by changing the area() so it returns the products of its input parameters. We can check that this change doesn't break anything by rebuilding the code and re-running the test, so let's do it. The program still works, so we can tick-off the first test from our list.

The second test is approached in the same way as the first. Again, we'll add our new test and re-build; again we'll get a compile error because the code hasn't been written yet. Adding a bit of code for this perimeter() function gets the program to build correctly, though hard-coding a return value of zero causes an assertion when it is run. Just to get things working we'll set the return value to 10 and rebuild. By now the code looks something like this:

class Quad {
public:
	Quad () {;}
	int area(int wd, int ht) { return wd * ht; }
	int perimeter(int wd, int ht) { return 10; }
};

void main()
{
	Quad q;
	assert( 6 == q.area(2,3));
	assert( 10 == q.perimeter(2,3));
}
The errors of our ways are beginning to show, so during the refactoring step we have a bit more work to do. The easiest way to remove duplication is to move the parameters for area and perimeter to the constructor and make the corresponding changes to the rest of the code:
class Quad {
	public:
		Quad (int wd, int ht) { m_wd = wd, m_ht = ht;}
		int area() { return m_wd * m_ht; }
		int perimeter() { return m_wd + m_wd + m_ht + m_ht; }
	private:
		int m_wd;
		int m_ht;
};

void main()
{
	Quad q(2,3);
	assert( 6 == q.area());
	assert( 10 == q.perimeter());
}
When we rebuild the program there are no errors and it runs without asserting, so our job is done. This might not be the best example of TDD or C++ programming, but it illustrates a number of things:
  • We wrote the tests first and only wrote new code when a test failed. Testing drives the development rather than any architectural considerations.
  • Eliminating duplication revealed a bad design decision, which we were able to quickly correct. Writing tests in this way clearly influences our design.
  • The tests made us more confident about making changes. The program was swiftly made to work and as we added functionality our tests keep it working.
  • We took very small steps, so small that even a novice C++ programmer could follow them. Perhaps, as an expert you could have taken larger steps. However, the technique is not dependent on a particular skill level or experience.
  • The tests document the code. Someone who wants to use 'Quad' has a number of working examples as a reference. In writing the tests we viewed 'Quad' from the perspective of a user, not just an implementer.
  • Our progress was measured by the tests we passed. There was no question of subjective measurement, like "it's almost done". Our tests either pass or fail, and we can get a snap-shot of our progress at any time by running the tests.
  • Duplication is a symptom of dependency, so removing duplication removes dependency and helps drives us towards designs consisting of loosely coupled components that are easy to test.
TDD establishes a rhythm to our work: the test fails (red), we fix the problem quickly (green), we then go back to eliminate duplication (refactor). The basic rules are (1) write new code only when a test fails, and (2) eliminate duplication.

Building the CppUnit Lite Test Framework

You can practice TDD without any tools, but it's hard. What we need is a labour saving device that will take the drudgery out of washing-up… sorry, testing! Fortunately, a number of such tools are available for download without charge. They mostly originate from a Smalltalk tool, called SUnit, which was developed by Kent Beck to illustrate how testing might be done in a XP project.

The test framework featured in this article, like many similar ones available for other development languages and environments, is based on JUnit; an improved version of SUnit ported to Java by Beck and Gamma . You'll undoubtedly learn more about TDD by building your own JUnit style framework, but I've taken the easy of option of using an existing Framework built for C++ by Object Mentor, called 'CppUnit Lite'.

The CppUnit Lite framework is distributed in a ZIP file that can be downloaded from www.objectmentor.com – resources, downloads. Once you've got the file on your PC, use a tool like PkZip to extract its contents into a suitable folder, say 'c:\Frameworks'. In the root folder (om) you'll find 'readme.txt' with instructions on how to build the framework as a static library. However, if you're using Visual Studio v6.0, then there is a pre-built example for you in the CppUnitTests folder (om\CppUnitLite) and this is the best place to start.

In the following section, I'll explain how the test framework works in the context of the pre-built example, CppUnitTests. You may find it useful to build and run this example on your own PC while following the explanation. If you can't build this project, however, don't worry, as we will build the framework as a static library and add it to a typical application in Part II of the article.

How CppUnit Lite Works

We'll start by stating 'what' a test framework should do, and then look at 'how' CppUnit Lite meets these requirements. Amongst the things a test framework should provide are:
  • Test Method: we want a quick and straightforward way to write a test case that clearly conveys its intent. Each test case should be independent from any other test case, which implies they can be executed in no particular order.
  • All Tests: eventually there may be hundreds or even thousands of test cases in a test suite, so we need a fool-proof way to ensure that they will all be run with the minimum of administrative effort.
  • Assertion: tests should either pass or fail, so some form of assertion statement must be supported. The assertion should give details about which test failed and why it failed.
  • Test Fixture: we don't want our test cases to be dominated by common code that simply initializes and de-initializes the objects for our test environment, so we need some place to setup objects (fixtures) and another place to recover them when the test is done.
CppUnit Lite provides a very easy way to write a test. Look at StackTest.cpp in the CppUnitTest folder and you will find an example of a test case to validate the creation of a 'Stack' object:
TEST( Stack, creation )
{
	Stack s;
	LONGS_EQUAL(0, s.size());
}
The implementation of the object under test is found in Stack.h and there is a self-evident connection between the file containing the test and the file containing the product being tested. To create a test for our Quad class, we would simply add the files (.cpp and .h) to the project (Project | Add to Project – Files ) and create a new test file ( File | New – C++ Source File) called QuadTest.cpp. There is good separation between test and production code.

Writing the test case is simply a matter of typing the macro TEST, creating the Stack object, and then checking that its size is zero. The macro's first parameter indicates the object being tested ('Stack') and the second parameter indicates the nature of the test ('Creation').

The smart stuff goes on inside the TEST macro, see Test.h in the CppUnitLite folder. It looks a bit scary, like most macros. However, it simply causes the pre-processor during the build to create a new class named 'creationStackTest', derived from the base class 'Test'. It then creates an instance of this class in your code and uses the body of the test you have just written as an implementation of its virtual function run().

Still confused? Well, type the following in place of the test case and you'll get the same effect; just think of the macro as a bit of magic that saves you some typing:

class creationStackTest : public Test
{
	public:
		creationStackTest () : Test ("StackTest") {}
		void run (TestResult& result_);
}
creationStackTest;

void creationStackTest::run (TestResult& result_)
{
	Stack s;
	LONGS_EQUAL(0, s.size());
}
The base class 'Test' does most of the administration you'll need to ensure your test cases are run. Its constructor invokes the AddTest() member function of the TestRegistry class to add the creationStackTest object instance to a linked list of test cases, such that each Test object has a pointer to the next Test object, and the TestRegistry object holds a pointer to the list's first Test object. There is a single TestRegistry object (accessed through its private member function Instance()) and this provides the means for running all of the test cases from the program's main():
int main()
{
	TestResult tr;
	TestRegistry::runAllTests(tr);
	return 0;
}
The static member function runAllTests() calls TestRegistry::run() which iterates through the linked list of test objects, calling for each one its virtual run() function. What this means is that just by writing a TEST macro you'll automatically ensure your new test case will be run whenever you execute the Test program; administration doesn't get much easier than this.

The assertion statements provided by the framework, such as LONGS_EQUAL, are again macros designed to save you some typing. The condition expressed in their parameter(s) is evaluated and upon fail a 'Failure' object is created and added to the TestResult object. For example, expanding the macro CHECK ( 2 < 1 ) gives:

if (! (2 < 1) )
{
	result_.addFailure (Failure
		(name_, __FILE__,__LINE__, 2 < 1));
	return;
}
In the above code fragment, 'result_' refers to the TestResult object passed from main(), via TestRegistry, to Test::run (abeit hidden in the TEST macro). The 'Failure' object takes the following parameters for its constructor; name_ from the name of the test object, file and line number for the macro's location, and finally the text of the condition formed by the CHECK macro's parameter. A number of similar macros are defined in Test.h in the CppUnitLite folder.

Unlike other types of assert you might have encountered, the ones defined in this test framework do not cause the program to abort or open any form of dialog box; they just create a Failure object and add it to the TestResult object. Therefore, while failure will cause the test case to stop (return), it does not affect the execution of any other test case in the suite. The test cases are isolated from each other, as they should be.

When TestRegistry::run() reaches the end of the list of test objects, it asks the TestResult object to print a summary of the test results by calling its member function testsEnded(). You may have noticed that the information in each 'Failure' was printed-out when TestResult::addFailure() was called. This technique of passing around an object to collect the results of some computation is described by a design pattern named the 'Collecting Parameter'.

CppUnit Lite does a fair job of implementing most of our requirements for a test framework. True, it doesn't implement the test setup() and teardown() functions we need for Test Fixtures, and we might like it to have a facility for organizing our test cases into a collection of test suites so we can run all (or some) of the test suites from a single program. We might also like some other improvements, such as having additional types of assertions and having a better user interface. However, let's not be too critical as CppUnit Lite does make writing tests very easy and has zero test administration overhead. It is also simple, lightweight, and it's free.

Conclusion

Test-Driven Development gives us confidence to change our code through a succession of small steps. You can adjust the size of your steps according to your experience and confidence in the change you are making, so it can be used by expert and novice alike.

TDD doesn't just ensure your code is rigorously tested, it also improve your design as it encourages you to view your classes from the perspective of future users: testing first drives you towards creating classes that are intrinsically easy to test. The rhythm of write a test that fails, fix the test, refactor the code gives you rapid feedback about your design decisions so problems are more quickly identified and fixed. It is a satisfying way to develop software and when used in conjunction with other Extreme Programming practices can be quite liberating.

In the next article we'll see how CppUnit Lite squares-up when it is used to develop a GUI application. Meanwhile, you might like to practice using it to develop some of your own classes by copying the way Stack.h and StackTest.cpp were used to develop a Stack class in the CppUnitTests project.


Will Stott is a Chartered Engineer who moved into software over 20 years ago after building a Nascom-1 and hand coding his first program in hex. Today he mainly programs in C++ and C# and is a firm advocate of agile process. Find him at www.willstott.com.

Simple Design and Refactoring

Simple design means that the simplest possible solution is adopted rather prematurely adding any form of complexity. This can only work as Test-Driven Development is guided by a shared metaphor, like the picture on the face of a jigsaw, which helps assembly of the pieces into a consistent whole; top-down design. However, the very nature of TDD drives you towards creating highly cohesive, loosely coupled components; bottom-up design. TDD and Extreme Programming (XP) encourage simple design, which is easy and safe to change, rather than complex design, which is difficult and risky to change.

Refactoring means changing existing code in order to banish duplication, make it simpler, more flexible, and easier to understand. Since developers are required to refactor whenever the opportunity arises there is collective ownership of the entire code base, so if you see a problem you are empowered to fix it. Refactoring might sound like a recipe for disaster, but it works because the team adopt other XP practices: Coding standards to facilitate communication, continuous integration to ensure everyone's work is built into the product several times a day, and testing meticulously to keep the product in a working state.

Flow and Rhythm

TDD has opened my eyes to the importance of establishing a degree of rhythm to my work. Its rapid cycle of write a test, fix the code, and refactor helps encourage a mental state where my work actually flows rather than goes with its usual stutter.

'Flow' is that very productive condition where the hours pass like minutes and you're totally focused on something you find intensely satisfying. Perhaps, you've experienced 'flow' for yourself. It appears to be a common phenomenon, and not just amongst programmers for writers, sports-men, painters, musicians, and even city traders also talk about 'flow' in their work.

TDD may be tapping into something deep in our make-up. Regardless of any such Zen properties, it certainly makes developing software more enjoyable and as a consequence makes the overhead of writing all these unit tests a little more acceptable.

Resources

  • www.objectmentor.com – a well-respected company providing mentoring and skill development for software development. An excellent source of material about XP and TDD, including the CppUnit Lite test framework.
  • www.sourceforge.net – provides free hosting to Open Source software development projects. A search for Test Driven Development yields over thirty test frameworks covering many common languages and environments.
  • www.junit.org – dedicated to software developers using JUnit or one of the other XUnit testing frameworks.
  • www.c2.com – home of the WikiWikiWeb containing lots of information about XP, TDD, and many other subjects that are of interest to developers. You should try c2.com/cgi/wiki?TestDrivenProgramming as a starting point.
  • www.pkzip.com – vendor of the PKZip tool for creating and extracting ZIP files.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Linux is only free if your time has no value” - Jamie Zawinski