October 13, 2006
Test Driven Development with Visual Studio 2005Test 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:
1) Never write a single line of code unless you have a failing automated test.
2) 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:
1) Create a Product and verify IsDirty is false.
2) Create a Product, change the Product Name, and verify IsDirty is true.
3) Create a Product, change the Product Name, verify the ProductName is changed.
4) 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 is the TDD mantra, and it describes the three states you go through when writing code using TDD methods.
-- Write a failing test.
-- Write the code to satisfy the test.
-- Improve the code without changing its functionality.
Alternately you could say, "Make it fail. Make it work. Make it better."