Guidelines for Test-Driven Development
Jeffrey Palermo
MCSD.NET
May 2006
Summary: Find out how to incorporate Visual Studio Team System into test-driven development practices emphasized in Agile development methodologies. (3 printed pages)
Contents
Introduction
Process Example
Benefits of Test-Driven Development
Characteristics of a Good Unit Test
Conclusion
See Also
Introduction
Test-driven development (TDD) is an advanced technique of using automated unit tests to drive the design of software and force decoupling of dependencies. The result of using this practice is a comprehensive suite of unit tests that can be run at any time to provide feedback that the software is still working. This technique is heavily emphasized by those using Agile development methodologies. In order to use this technique with Visual Studio Team System, you must understand some other topics:
- Creating and running automated tests inside the VSTS IDE
- Abstracting dependencies in an object-oriented world
- Refactoring new and old features to remove duplication in code
- How to: Author a Unit Test
- How to: Organize Tests into Test Lists
- How to: Run Selected Tests
The motto of test-driven development is "Red, Green, Refactor."
- Red: Create a test and make it fail.
- Green: Make the test pass by any means necessary.
- Refactor: Change the code to remove duplication in your project and to improve the design while ensuring that all tests still pass.
The Red/Green/Refactor cycle is repeated very quickly for each new unit of code.
Process Example
When you use Visual Studio Team System, the following steps can be performed while you are processing a work item that is already assigned to you.
- Make sure that you have a VSTS Test Project in your solution available for creating new tests. This project should reference the class library in which you intend to add new functionality.
Follow these steps (slight variations exist among TDD practitioners):
- Understand the requirements of the story, work item, or feature that you are working on.
- Red: Create a test and make it fail.
- Imagine how the new code should be called and write the test as if the code already existed. You will not get IntelliSense because the new method does not yet exist.
- Create the new production code stub. Write just enough code so that it compiles.
- Run the test. It should fail. This is a calibration measure to ensure that your test is calling the correct code and that the code is not working by accident. This is a meaningful failure, and you expect it to fail.
- Green: Make the test pass by any means necessary.
- Write the production code to make the test pass. Keep it simple.
- Some advocate the hard-coding of the expected return value first to verify that the test correctly detects success. This varies from practitioner to practitioner.
- If you've written the code so that the test passes as intended, you are finished. You do not have to write more code speculatively. The test is the objective definition of "done." The phrase "You Ain't Gonna Need It" (YAGNI) is often used to veto unnecessary work. If new functionality is still needed, then another test is needed. Make this one test pass and continue.
- When the test passes, you might want to run all tests up to this point to build confidence that everything else is still working.
- Refactor: Change the code to remove duplication in your project and to improve the design while ensuring that all tests still pass.
- Remove duplication caused by the addition of the new functionality.
- Make design changes to improve the overall solution.
- After each refactoring, rerun all the tests to ensure that they all still pass.
- Repeat the cycle. Each cycle should be very short, and a typical hour should contain many Red/Green/Refactor cycles.
Benefits of Test-Driven Development
- The suite of unit tests provides constant feedback that each component is still working.
- The unit tests act as documentation that cannot go out-of-date, unlike separate documentation, which can and frequently does.
- When the test passes and the production code is refactored to remove duplication, it is clear that the code is finished, and the developer can move on to a new test.
- Test-driven development forces critical analysis and design because the developer cannot create the production code without truly understanding what the desired result should be and how to test it.
- The software tends to be better designed, that is, loosely coupled and easily maintainable, because the developer is free to make design decisions and refactor at any time with confidence that the software is still working. This confidence is gained by running the tests. The need for a design pattern may emerge, and the code can be changed at that time.
- The test suite acts as a regression safety net on bugs: If a bug is found, the developer should create a test to reveal the bug and then modify the production code so that the bug goes away and all other tests still pass. On each successive test run, all previous bug fixes are verified.
- Reduced debugging time!
Characteristics of a Good Unit Test
A good unit test has the following characteristics.
- Runs fast, runs fast, runs fast. If the tests are slow, they will not be run often.
- Separates or simulates environmental dependencies such as databases, file systems, networks, queues, and so on. Tests that exercise these will not run fast, and a failure does not give meaningful feedback about what the problem actually is.
- Is very limited in scope. If the test fails, it's obvious where to look for the problem. Use few Assert calls so that the offending code is obvious. It's important to only test one thing in a single test.
- Runs and passes in isolation. If the tests require special environmental setup or fail unexpectedly, then they are not good unit tests. Change them for simplicity and reliability. Tests should run and pass on any machine. The "works on my box" excuse doesn't work.
- Often uses stubs and mock objects. If the code being tested typically calls out to a database or file system, these dependencies must be simulated, or mocked. These dependencies will ordinarily be abstracted away by using interfaces.
- Clearly reveals its intention. Another developer can look at the test and understand what is expected of the production code.
Conclusion
Test-driven development is an advanced technique that uses unit tests to drive the design of software. This article outlines the main points of the technique, but there are entire books written on the topic.
You can use Visual Studio Team System to unit-test software with or without using test-driven development. The individual team should decide what is best for them.
See James Newkirk's book Test-Driven Development in Microsoft .NET, published by Microsoft Press.
See Also
About the author
Jeffrey Palermo is a senior software developer at DataCert, Inc., an ISV that uses Agile development methodologies. He serves as a Director of the Austin .NET User Group and is the INETA membership manager for south Texas. Jeffrey holds the MCSD for .NET certification. Visit his blog at https://codebetter.com/blogs/jeffrey.palermo/.