Test Driven Development (TDD) is not a new concept. In fact, the idea of test-first,
code-second has been around for many years. In the latest release of Microsoft's
premier developer tool, Visual Studio 2005 Team System many new features have been
added, including features for testing software. What does this mean to you, the
serious developer? It means you now have integrated unit testing that can be leveraged
for Test Driven Development.
What is Test Driven Development?
I like to think of TDD as a means to design software by first documenting the requirements
in a language a programmer understands, and then writing the code to fulfill the
requirements. In his book
Test-Driven Development: By Example (Addison-Wesley), Kent Beck defined
TDD with two simple rules:
- Never write a single line of code unless you have a failing automated test.
- Eliminate duplication.
Kent's rules and my idea of TDD go hand-in-hand. In most typical software development
cycles, a product owner or project manager will provide a set of requirements; typically
the requirements are in a Word document titled "Functional Specification." Word documents
are great for conveying a story, but what we really need to do is translate the
story told by the project manager into a programming language - think of this as
a new requirements document written in C# (or Visual Basic). The requirements are
written in code as tests; each test representing a single requirement. In addition,
when we get to writing the functional code (a little later) we want to ensure we
eliminate any duplicate code by centralizing code that is needed in more than one
place.
Types of Tests
Every time I start talking to people about software tests their mind seems to go
immediately to thinking about a test to ensure the application works as expected.
This is certainly important, and it can be broken into two parts.
- Programmer Tests
Automated tests written by the programmer prior to writing the functional code.
The functional code is written to do no more and no less than pass the test. These
are also know as unit tests.
- Customer Tests
Tests written by a test engineer to validate the functionality of the application
from the customer perspective. These are also known as acceptance tests.
For the remainder of this article I will be talking solely about Programmer Tests,
which, even though they include the word "test," are the responsibility of the programmer.
No Less, No More
In the definition of Programmer Tests, I mentioned that the functional code should
do no more and no less that what the programmer test indicates. This is a lot more
difficult than it sounds. It is a natural tendency, especially for programmers,
to attempt to foresee the future. How many times while writing code have to included
a "feature" because you know that eventually it would be requested. Perhaps you
wrote a method to get customer order summaries, even though that was not part of
the current specification. Certainly this would be needed soon.
This is the trap we have to avoid. Albert Einstein once said, "Any fool can make
things bigger, more complex, and more violent. It takes a touch of genius —
and a lot of courage — to move in the opposite direction." In other words,
truly brilliant code includes on the code necessary to execute the requirements,
and no more. The functional code should pass all of its programmer tests, communicate
its purpose clearly, and contain the smallest number of classes and methods possible.
When the code does all of these things it is done. Step away from the keyboard.
Resist the urge to add more.
That's Great, But How Does It All Work?
As a programmer committed to using TDD well, the first thing you need to do is document
the functional requirements as programmer tests. Do this before any functional code
is written. Start by creating a Test List. Describe all of the tests that define
the requirements, and ensure this list is the most complete definition of the requirements
criteria. Ensure the Test List is as simple as it can be while conversely ensuring
all of the requirements are documented.
For example, let's say as part of our application we need to create an object to
represent a Product. The Test List may look something like this:
- Create a Product and verify IsDirty is false.
- Create a Product, change the Product Name, and verify IsDirty is true.
- Create a Product, change the Product Name, verify the ProductName is changed.
- Create a Product, change the Product Name, save the Product, verify the Product was
saved.
This is a simplified version of a Test List, and depending on the requirements there
may not be much more to document.
Red/Green/Refactor
Red/Green/Refactor is the TDD mantra, and it describes the three states you go through
when writing code using TDD methods.
- Red
Write a failing test.
- Green
Write the code to satisfy the test.
- Refactor
Improve the code without changing its functionality.
Alternately you could say, "Make it fail. Make it work. Make it better."
Let's take a look at these steps in action.
Writing the Programmer Test
Let's satisfy the first requirement (test) in our Test List. In Visual Studio 2005
Team System, assuming you have the Developer Edition or Test Edition, you can create
a new kind of project, a Test Project.

The test project produces a few artifacts, and I am going to focus on the UnitTest1.cs
file. This file is where you will document your requirements in C#. Delete the ManualTest1.mht
file - it is not needed for this project.
using System;
using System.Text;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace DotNetJunkiesTDD {
[TestClass]
public class ProductTest {
public ProductTest() {
// TODO: Add constructor logic here //
}
[TestMethod]
public void TestMethod1() {
// TODO: Add test logic here
}
}
}
In the
preceding code block you see the basic structure of a test class. This is a class
that is specifically for containing programmer (unit) tests. The file includes a
reference to the Microsoft.VisualStudio.TestTools.UnitTesting namespace. This brings
all of the unit testing utilities into this class.
The
class definition is modified with the [TestClass] attribute. This specifies this
class as a collection of unit tests (if you've even used NUnit, this is the equivalent
of TestFixture).
The method declaration is modified by the [TestMethod] attribute. This specifies this method as a test for a single unit of functionality. A single feature (such
as a Product representation in code) may contain several programmer tests
(in NUnit this is a Test).
Red
To write the first requirement as a programmer test, you modify the TestMethod1
method as shown here:
/// <summary>
/// Create a Product and verify IsDirty is false.
/// </summary>
[TestMethod]
public void CheckIsDirty()
{
Product prod = new Product();
Assert.IsTrue(prod.IsDirty);
}
In the
preceding code you define the method CheckIsDirty as a TestMethod.
In the test
you write only the code to define the test criteria. In this case you create a new
instance of the Product class, and use the Assert.IsFalse()
method to make the assertion
that the IsDirty property has the value of false. Of course if you try to compile
this code it will fail because you have not defined the Product class. At this stage
you are Red - you have a failing test (failing to compile is failing).
Green
The next step is to write only the code required to fulfill the requirement (to
make the test pass). To do this you can create a new ClassLibrary project where
the Product class will reside. Create a Product class.
using System;
using System.Collections.Generic;
using System.Text;
namespace MyLibrary {
public class Product {}
}
After creating the Product class (and adding a reference to the ClassLibrary project
in our Test project), the Test project still wont compile because we still have
not fulfilled the test. We need to satisfy the requirement for an IsDirty
property
to return false.
public class Product
{
public bool IsDirty = false;
}
Now, the preceding code meets the requirements as they are defined. It would be
real easy for me to add some additional code (like the possibility to return true
from this property), but I haven't reached the requirement for that yet.
One of the key principles to TDD is the small, incremental process for developing
software. You document a requirement in a test; write only the code to fulfill the
test; clean up the code if necessary; move on to the next requirement. It takes
a great deal of discipline to write ONLY
the code necessary to fulfill the requirements
(i.e. make the test pass).
Now that we have this code, both projects should compile. Once the projects compile
you can run your programmer test to see if they pass (to see if you fulfilled the
requirements). Click the green right-facing arrow in the VSTS toolbar, as shown
here:

When the tests run, the Test Results window opens, the test has an "!" icon to its
left indicating the test is about to run. Once the test runs you will see the icon
change to a green circle with a check, or a red circle with an X. Green indicates
the test passed, while red indicates it failed (are you starting to see where Red/Green/Refactor
comes from?).
Since our functional code is exactly the code required to pass the test, we get
a green icon and we can move on.
Refactor
The next step is to refactor our code if possible. Going back to Kent Beck's rules,
this is the "eliminate duplication" rule. Recall, the definition of refactor is
to improve the code without changing its functionality. Reviewing the code we have
written we can see that there is no refactoring needed.
Repeat and Rise
At this point you can go back to your Test List and write your programmer test to
represent the next requirement.
/// <summary>
/// Create a Product, change the Product Name, and verify IsDirty is true.
/// </summary>
[TestMethod]
public void ChangeProductName() {
Product prod = new Product();
prod.Name = "New name";
Assert.IsTrue(prod.IsDirty, "After changing Product.Name, Product.IsDirty
should be true" );
}
The preceding test fails to compile because we have no Name property. The next
step is to write only enough code for the projects to compile.
public class Product
{
public bool IsDirty = false;
public string Name;
}
After writing this code the projects both compile. Next you run your unit tests
- CheckIsDirty passes (nothing changed to break it), but ChangeProductName
failes.

While the code compiled, we still haven't implemented enough to fulfill the requirement
(we are in the Red state). So, we add only enough code to do that.
public class Product
{
public bool IsDirty = false;
public string Name {
set
{
IsDirty = true;
}
}
}
After compiling both projects, both tests pass. We have now fulfilled two requirements.
Time to refactor. As we look at the code, the functional code looks as clean as
it can be, but there is some duplicate code in the Test project. We can refactor
the Test code as follows (yes, we refactor test code too):
[TestClass]
public class ProductTest {
public ProductTest() {
// TODO: Add constructor logic here
}
Product m_prod;
[TestInitialize]
public void Init()
{
m_prod = new Product();
}
/// <summary>
/// Create a Product and verify IsDirty is false.
/// </summary>
[TestMethod]
public void CheckIsDirty() {
Assert.IsFalse(m_prod.IsDirty);
}
/// <summary>
/// Create a Product, change the Product Name,
and verify IsDirty is true.
/// </summary>
[TestMethod]
public void ChangeProductName() {
m_prod.Name = "New name";
Assert.IsTrue(m_prod.IsDirty, "After changing Product.Name,
Product.IsDirty should be true");
}
}
You can use the [TestInitialize] attribute to modify a method to be executed prior
to every test method that is run (the [ClassInitialize] attribute will cause the method
to be executed once prior to when the first test begins).
We have completed our Red/Green/Refactor steps. Now we can move on to the next test
in our Test List.
/// <summary>
/// Create a Product, change the Product Name, verify the ProductName is changed.
/// </summary>
[TestMethod]
public void ChangeProductNameVerify()
{
string prodName = "Seven's Death Ray";
m_prod.Name = prodName;
Assert.AreEqual(prodName, m_prod.Name,
"After changing Product.Name, the value should be the same as the value passed in."
);
}
Notice
we are making a new assertion, the AreEqual assertion. Be sure to
check
the documentation
to learn about all of the possible assertions you can make.
Our code doesn't compile because the Product.Name property doesn't have
a get accessor,
so we need to add one.
public class Product
{
public bool IsDirty = false;
public string Name {
get
{
return null;
}
set { IsDirty = true; }
}
}
While
our code now compiles, the new test fails. The Name property is returning
null when "Seven's Death Ray" is expected. So, we update the code.
public class Product
{
public bool IsDirty = false;
private string m_name;
public string Name {
get { return m_name; }
set {
IsDirty = true;
m_name = value;
}
}
}
Now
all of our tests pass. We again look for refactoring opportunities, and then move
on to the next test.
Summary
In this article you learn about Test Driven Development and how to write software
using TDD with Visual Studio 2005 Team System. Hopefully you can see that with a
little discipline, and a willing ness to do things a little differently than what
you are used to, you will be able to develop software knowing that you are writing
only the code that is necessary. Additionally you will write code with the confidence
that nothing you added, changed or removed cause a breaking change; if all the tests
pass, then the code meets its requirements.
I truly hope you are willing to give TDD a try. I think you will find it addictive.
It may seem cumbersome, but in reality it isn't, and the pay off for a little extra
work is absolutely worth it. In this article it may have seemed like a lot of steps,
and time taken to write test-first code. In reality the tests and code in this article
took only a few seconds each to complete - it just seemed like longer because I
was explaining each step of the way.