Skip to main content

Demystifying Extreme Programming: Test-driven programming

Keep it simple by writing tests before you write code

Roy Miller, Software Developer, RoleModel Software, Inc.
Roy W. Miller has been a software developer and technology consultant for almost ten years, first with Andersen Consulting (now Accenture) and currently with RoleModel Software, Inc. in North Carolina. He has used heavyweight methods and agile ones, including XP. He co-authored a book in the Addison-Wesley XP Series (Extreme Programming Applied: Playing to Win) and is currently writing a book about complexity, emergence, and software development (the working title is Growing Software: Debunking the Myth of Prediction and Control). Contact Roy at rmiller@rolemodelsoft.com. or roy@roywmiller.com.. You can also visit his personal Web site at www.roywmiller.com.

Summary:  Test-driven programming is one aspect of XP that baffles programmers. Many of us make incorrect assumptions about what it means and how to do it. This month, XP coach and Java developer Roy Miller talks about what test-driven programming is about, why it can revolutionize your productivity and quality as a programmer, and the mechanics of writing tests.

View more content in this series

Date:  22 Apr 2003
Level:  Intermediate
Activity:  5002 views

For the last fifty years, testing has been viewed as something that gets done toward the end of a project. Sure, there may be integration testing while the project is going on, and testing doesn't usually start after all the coding is done, but it's usually in a later phase. However, proponents of XP suggest that this model is completely backwards. As a programmer, you should write tests before you write code, then write just enough code to get the tests to pass. Doing that will help you keep your system as simple as possible.

Writing tests first

XP talks about two kinds of tests: programmer tests and customer tests. Test-driven programming (also called test-first programming) most commonly refers to the first variety, at least when I use the term. Test-driven programming is letting programmer tests (or unit tests -- again, just a choice of terms) drive the code you write. That means you have to have the test before you write the code. The test drives the code you write by dictating what code you need to write. You write only the code necessary to make your test pass -- no more, no less. The XP rule is simple: If you don't have a programmer test, you don't know what code to write, so you don't write any code.


How to write tests first

All this theory is great, but how do you write a test first? First, I recommend you read Kent Beck's book Test-Driven Development: By Example (see Resources) for an extensive, book-long example. It talks not only about the mechanics of how to write tests and allow them to drive your code, but also why test-driven programming is such a good thing. I'll give a simple example here, to give you a taste of what I'm talking about.

Test-driven versus test-first

I prefer using the term "test-driven" to "test-first," because test-first focuses on the mechanics of writing programmer tests before writing code. Those mechanics are important, but the real power is the change in thought and programming habits that test-driven implies. The more comprehensive term, "test-driven programming," encompasses both kinds of tests, and refers to the way XP teams focus on having tests drive pretty much everything they do.

Suppose I'm writing a system that has Person objects. I want each Person to tell me its age (as an integer) when I ask. Even though I haven't written a lick of code yet, it's time to write a test. "What?," you might say, "I don't even know what I'm testing. How can I write a test?" The short answer is that you do know what you're testing, you just don't know you know because you're not used to thinking that way. Here's what I mean.

You don't have any code yet, but you have an idea what the Person object should look like. It should have a method on it that returns the age as an integer. Because I work most often with the Java language, I use JUnit to write programmer tests. Listing 1 shows the JUnit test I would write for the Person object:


Listing 1. JUnit test for the Person object
package com.roywmiller.testexample;

import junit.framework.TestCase;

public class TC_Person extends TestCase {

	protected Person person;

	public TC_Person(String name) {
		super(name);
	}

	protected void setUp() throws Exception {
		person = new Person();
	}

	public void testGetAge() {
		int actual = person.getAge();
		assertEquals(0, actual);
	}

	protected void tearDown() throws Exception {
	}

}

First, let's cover some raw mechanics for anyone unfamiliar with JUnit. The TestCase class is the one you'll use the most. You just write a test class (TC_Person in this example) that subclasses TestCase. (Note: In JUnit 3.8.1, the constructor taking a String can be there or not, but because I do almost all of my Java development in the Eclipse IDE (see Resources) and it gives me the constructor for free, I leave it in.) Once you have your test class, the real action happens in your test methods. These, fittingly, start with the prefix test (they have to be public and return void). When you run a test, JUnit:

  • Introspects your test class and executes each method starting with "test"
  • Executes the setUp() method before each test method
  • Executes the tearDown() method after each test method

In this example, there isn't much going on in the setUp() method. It simply instantiates a Person (I included it so you could see what a "full" test case looks like). What that means is that if I had 20 test methods here, each one would start with a new Person instance. There's nothing to do in tearDown(), so it's empty for now. It's worth emphasizing that you don't need a setUp() or a tearDown(); I usually don't create them until I write my second or third test method and determine that there's some common setup or tear-down activity they all share.

With the mechanics out of the way, note that I made a few design decisions in my test method. I assumed that I could construct a person and that the "default" Person would give me back an age of 0. I also assumed the Person object would have a getAge() method. Those assumptions aren't bad for now, even though they won't last. This is a simple test to get me going, to "prime the pump," so to speak. Given those assumptions, I instantiated a Person (in setUp() just to illustrate how that method gets used), called the method I'm testing in the test method, and called one of the bundle of "assert" methods. Assert methods test whether something is true. In other words, they make an assertion about something that tells JUnit to validate that it holds true. Table 1 contains a list of assertion categories:


Table 1. Assertion categories
Assert methodDescription
assertEqualsCompares two things for equality (primitives or objects)
assertTrueEvaluates a boolean for true
assertFalseEvaluates a boolean for false
assertNullChecks that an object is null
assertNotNullChecks that an object isn't null
assertSameChecks that two objects are the same instance
assertNotSameChecks that two objects are not the same instance

In this case, I checked that the age my Person instance gave me was 0, the default for new Person objects.

Of course, this test won't even compile. Figure 1 shows the JUnit Fast View when I try to run it in Eclipse.


Figure 1. JUnit compile failure
JUnit compile failure

Obviously, I don't have a Person class yet so my test is having some trouble running -- JUnit gives me a red bar. If everything ran and the test passed, the bar would be green. You're always shooting for a green bar. The JUnit motto, after all, is "keep the bar green to keep the code clean" (groaning is permitted).

No problem. I'll make the Person class, as shown in Listing 2:


Listing 2. Person class
package com.roywmiller.testexample;

public class Person {
	
	public int getAge() {
		return 0;
	}

}

Now when we run the test, it passes and I should see a green bar. I had to return something from getAge() or it wouldn't compile. It just so happens that 0 was convenient, and it's supposed to be the default for new Person instances, so that works out well. Again, I wrote just enough code to get the test to pass.

Being able to have a Person with a default age is nice, but that doesn't help my system much. Person needs to be smarter than that. What I really need is a Person that holds its birth date and answers its age as of today. That means my Person object needs to grow. Before I go code something, I rename testGetAge to testGetDefaultAge (to make it clear that I was testing the default age there) and write another test method for my test case, as shown in Listing 3:


Listing 3. New test method
public void testGetAge() {
	GregorianCalendar calendar = new GregorianCalendar(1971, 3, 23);
	person.setBirthDate(calendar.getTime());
	int actual = person.getAge();
	assertEquals(31, actual);
}

This test doesn't compile yet (are you noticing a pattern?) because there is no setBirthDate() method on Person. After I make one, Person will look like Listing 4:


Listing 4. Updated Person class
package com.roywmiller.testexample;

import java.util.Date;

public class Person {
	
	protected Date birthdate;
	
	public int getAge() {
		return 0;
	}
	
	public void setBirthDate(Date aBirthDate) {
		this.birthdate = aBirthDate;
	}

}

Person doesn't do anything different for getAge() yet, so the test fails. Figure 2 shows what appears in the JUnit Fast View:


Figure 2. JUnit assertion failure
JUnit assertion failure

An AssertionFailedError is generated telling me that instead of a 31, the result is 0. This failure is the expected result because I didn't change the method to do anything different. Now I can write just enough code to get the tests (there are now two) to pass. I have to allow a default age of 0, but I have to calculate the age for somebody born on March 23, 1971. Some programmers (including Kent Beck) would recommend simply doing something simplistic at this point, such as checking birthdate to see if it's null -- returning 0 if it is and 31 if it isn't -- then writing another test to force the calculation to get smarter. This is a fine technique to practice when thinking in small steps, and it's fine when you want to go back to basic disciplines to get yourself out of a debugging rut. But in this case I want to keep the example somewhat simple, so I'll just try to get this test to pass by calculating ages the way I want to, with a Calendar. Listing 5 shows what I would write in Person:


Listing 5. getAge() implementation
package com.roywmiller.testexample;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

public class Person {
	
	protected Date birthdate;
	
	public int getAge() {
		if (birthdate == null)
			return 0;
		else {
			int yearToday = 
			  Calendar.getInstance().get(Calendar.YEAR);
			
			Calendar calendar = new GregorianCalendar();
			calendar.setTime(birthdate);
			int birthYear =calendar.get(Calendar.YEAR);
			
			return yearToday - birthYear; 
		}
	}
	
	public void setBirthDate(Date aBirthDate) {
		this.birthdate = aBirthDate;
	}

}

When I run the tests, I get a failure that tells me 31 was expected, but the result was 32. What's wrong? Well, I know the problem must be in the code I just wrote, and there's not much going on there. When I check the else clause, I see that I was calculating age based on the year only. That won't do. I'm currently 31, but I'll be 32 later this month (as I wrote this code, it was March) and my algorithm gives me the wrong result. So I need to take that into account in getAge(). I'll use the snippet shown in Listing 6 to fix the problem:


Listing 6. Corrected getAge()
else {
	int yearToday = Calendar.getInstance().get(Calendar.YEAR);
	
	Calendar calendar = new GregorianCalendar();
	calendar.setTime(birthdate);
	int birthYear = calendar.get(Calendar.YEAR);
	
	if (yearToday == birthYear)
		return yearToday - birthYear;
	else
		return yearToday - birthYear - 1;
}

Green bar! There's a bit of code duplication in the Person class, but I'll leave that for a later refactoring exercise. Feel free to clean it up for me. You can do that with confidence because you have a test to run to confirm you didn't break anything.

This example should give a you a taste of what test-driven programming is like. I wrote just enough code at each step to get the tests to pass. This is as much a mindset challenge as a mechanical one. You have to get used to the idea that you can and should write tests before you write code. When all the tests pass, you're done.

When writing tests first, you have to get used to being intentionally shortsighted. The Listing 6 example is a trivial case. Real-world programming problems are usually a bit more complicated, even at their simplest. It helps to break those problems down into more manageable bits, but you might still end up with some complicated headaches. In those cases, you have to resist the urge to think too far ahead and make assumptions about how "generic" this ought to be, or how it ought to handle some as-yet-unseen case. Just write a test and get it to pass. You need to take small steps and write tests that force you to take more steps. Remember, you're testing your code into existence -- if you do that in small steps, you'll stay on track.


Why you should do it . . .

Maybe you don't think writing tests first is such a great idea. It might seem too strange, or maybe it seems unnecessary. I've heard the second reason frequently, mostly from experienced programmers. These folks are smart, they have lots of experience, and they say they don't need to write tests first because they know what they're doing. I can empathize, but I question an implicit assumption they're making: that they can't learn anything from writing tests first. I respectfully disagree. In fact, I think there are three reasons you should write tests first:

  • Learning
  • Emergence
  • Confidence

Writing tests first -- and having those tests later -- is a better way to learn. It keeps you focused on the interfaces for the things you write. When you write the test, you're pretending the class you're using already exists, then you're using that class the way you'll want to in the rest of the system. Later, when you forget how to use that class, you can look at the tests and see a very specific example. It's a great way to learn.

One of the most interesting things about writing tests first is that it helps make emergence possible. You're growing the system you're creating. If you're using XP, you haven't designed the whole thing up front -- you're designing and growing as you go. As you write tests first and get them to pass, you're letting the code tell you what it wants to do and become. If you just start coding stuff, you'll simply build your assumptions in. The more you defer those decisions, the greater your opportunity to discover new things and new directions that can make your system better.

My favorite benefit of writing tests first, though, is having them later. When I write tests first, I have fantastic logic coverage. I might not cover every path through the code, but I'll cover many of them. In any case, I'll have a suite of tests that are better than most pre-XP projects I ever worked on. I can run those tests at the press of a button. A few seconds later, I know whether my code runs like I said it should. That kind of regression suite is priceless. Anybody on my team (or elsewhere) can change the code at any time, even the day before release, because the tests tell them immediately if something broke. That gives me confidence as a programmer -- more confidence than most programmers are used to having.


. . . and why people don't

Many programmers who don't write tests first don't even know it's an option. If they know it is, they might be confused about how to do it or they might wonder why they should. Even if they know how and think it's a good idea, many people still don't write tests first.

Writing tests first takes discipline. There are plenty of times as a programmer when I think it would be easier not to write a test for whatever I'm working on. There are some times when that's true, but usually only for the short term. If I frequently don't write tests, then pretty soon I've got a pile of code that isn't covered by tests. When I'm coding the next system feature and something doesn't work right, where's the problem? Without tests I can't answer that question with confidence. Even if everything seems to work fine, I'm not sure I didn't break something else in the system that won't show up until later. That kind of cycle of doom is why most programmers hate testers who tell them something is wrong with the code. Tracking down bugs without tests is a recipe for late nights and job dissatisfaction.

When I explain it that way, most programmers I've talked to think test-driven programming is a good idea -- then they still don't do it. Writing a test before the code means you don't get to do the fun part of the job until the test runs and fails. Don't fall into this trap; you could pay dearly later on.


Tough cases

Invariably when people start writing tests they come across some situations where they say, "There's just no way to test this." There are some people in the XP community who would state unequivocally that you should never write any code without a test. You should knock yourself out trying, but in my own experience I have found some times where I can't do that. If you find yourself in that situation, should you give up? Sort of. I think there are two things you can do:

  • Write code before you write a test
  • In rare cases, don't write a test at all and come back to it later

If I find I can't write a test first after trying to do so, I back into the test. I still want to have a test so I can have the confidence that comes from a full regression suite, but I have to write some code first, then write a test. Sometimes I write a little code, then write a little test, and they evolve together. In some rare instances, I just can't figure out how to write a test at all. When that happens, and my pair partner can't figure it out either, I ask somebody else (from another pair, for example) if they have any bright ideas. Sometimes that does the trick. Other times, though, the entire team is stuck. In those cases, I have to choose practicality. I could stop coding and despair, or I could write some code without a test and come back to it later. Maybe the first bug that shows up in that code will make it more clear what to test and how. Practicality rules.


Testing tools and techniques

There is an xUnit library for almost every language under the sun. JUnit does the job for the Java platform very well. I personally use the Eclipse IDE (see Resources), which integrates JUnit extraordinarily well. Eclipse is open source and has its own suite of tests you can run. With the right foundation, you can write a lot of great tests. But sometimes it's good to have a little more help. Fortunately, there are some coding techniques that can make it much easier to test things, even things that appear to be untestable. A few techniques you can use are the ObjectMother pattern, Mock Objects, and Sham objects.

ObjectMother pattern

The ObjectMother pattern -- which is really an implementation of the Gang of Four Abstract Factory pattern (see Resources) -- tells you to create a factory object to give you instances of things you need for testing. For example, suppose you're building a system to take customer reservations for seminars. You might build an ObjectMother object that makes Seminar objects with different sets of characteristics that you can use to test certain things. In the Java language, you might create an object called TF_Seminar that has several static factory methods on it -- perhaps named createSomething or newSomething. The "something" would be replaced with some description of the thing you're creating, such as newFullyLoaded to create a Seminar with all data members populated with known data. Doing this puts your test data in one place and makes your code cleaner and easier to refactor. In the code, whenever you need a fully loaded Seminar for testing, you would do something like Listing 7:


Listing 7. ObjectMother example
Seminar seminar = TF_Seminar.newFullyLoaded;
seminar.doSomething();
assertEquals("expectedValue", seminar.getValue());

Mock objects

Mock objects allow you to mock up objects for testing (see Resources). You give the mock objects an interface you want the real components to have, then you use the mocks until the real components take shape. But mocks are more than stubs for nonexistent components. You can evaluate how your code is interacting with mocks (such as verifying how many times a method was called, check state, and so on). Mock objects have gotten a lot of publicity lately, but I think they're overused and often too heavy to be practical.

Sham objects

Sometimes all I want is a fake object that implements the same interface and can answer some specific questions about how I interact with it in a test. That's what a sham object is -- a lightweight way to fake things out in your tests. A sham object can be whatever you need it to be. It's the most versatile tool, pattern, and way of thinking I've ever used, and I recommend it as a tool in your toolbox. For example, a sham object for the Person object I created before would look something like Listing 8:


Listing 8. A sham for Person
protected class ShamPerson extends Person {
	protected boolean getAgeWasCalled;
	
	public int getAge() {
		getAgeWasCalled = true;
		return 25;
	}
}

Whenever practical, I try to include the sham for the class I'm testing (ShamPerson in this case) as an inner class within my test. Doing so hides the sham from other things that don't necessarily need it.

Once I have the sham, I can use it in tests where I'm not testing Person directly, but I am testing how other code interacts with a Person instance. I can instantiate a ShamPerson, then interact with it, then assert that getAgeWasCalled is true.


A programming revolution

Writing tests before I write code has revolutionized my life as a programmer, and it can do the same for you. My code is consistently simpler, cleaner, and more robust than it was before I wrote tests first. Simply having the discipline to think about how to test my code before writing it makes it better. If every software development team rejected every other XP practice and just wrote tests first, the software development world would be shockingly better. With a little practice, any programmer can write tests first. The tools (JUnit, Eclipse, and so on) are free, it only takes practice. I have seen the investment in time pay off, and I'm confident you will, too.


Resources

About the author

Roy W. Miller has been a software developer and technology consultant for almost ten years, first with Andersen Consulting (now Accenture) and currently with RoleModel Software, Inc. in North Carolina. He has used heavyweight methods and agile ones, including XP. He co-authored a book in the Addison-Wesley XP Series (Extreme Programming Applied: Playing to Win) and is currently writing a book about complexity, emergence, and software development (the working title is Growing Software: Debunking the Myth of Prediction and Control). Contact Roy at rmiller@rolemodelsoft.com. or roy@roywmiller.com.. You can also visit his personal Web site at www.roywmiller.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10799
ArticleTitle=Demystifying Extreme Programming: Test-driven programming
publish-date=04222003
author1-email=rmiller@rolemodelsoft.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers