We have started using Test Driven Design (hopefully properly) over the last few months and I wanted to share our experiences of it. Before we started using TDD, our approach to unit testing was to find a unit of work that we had just completed and write a test to make sure the output of the method was what we expected. The most obvious problem with this is that while we are hoping that the unit test will be testing the method, actually what we were doing was testing the method and everything that used it. So in this case, we were testing any service calls, database calls, any other classes and objects that are used in the method.
Looking at How TDD Works
The easiest way of understanding what test driven design is, can be achieved by looking at the following diagram:
Please note that I have obtained this image from Wikipedia (the article can be found here) To summarize:
- Before writing any code to solve the issue: write a test to test the functionality. If the test passes, then the functionality exists or the test has not been written correctly.
- Run the tests to make sure that this does fail.
- Write only enough code to make the test pass.
- Run all the tests to make sure that the test passes. All the tests should be run to make sure that the new code does not break the existing tests.
- Refactor and clean up the code.
The Role of Mocks and Stubs
To understand what a mock object is, it is a good idea to see what they do in the context of a unit test. I have said previously that a bad test is a test that unwittingly tests the objects that the unit of work uses, so databases, external classes, etc. A good way of only testing the code in the method would be to create fake objects that return what you expect it to. This eliminates their influence on the logic by limiting the external factors. I found a really good answer on stackoverflow on what the difference is between a mock and a stub: answer.
Bad Unit Test Designs
From what I have seen, a lot of tests that have been written in the past are integration tests. An integration test is a test that tests the integration (I know it is obvious, but I still thought I would make it clear) of the system to another system. Most often, this is used to test the integration of the application being tested and a database. I am not saying integration tests are a bad thing, what I am saying is that integration tests masquerading as unit tests are a bad thing. The reason for this is that a unit test should test the small unit of work that the code handles and not the external factors on the method. These tests make it hard to understand what is being tested and what is not. The other issue is that they are extremely brittle: since they rely heavily on external factors, if any of the objects change, the test will break.
Benefits of Test Driven Design
- When you write a test to describe the way a unit of work should work, it helps you only write code to make the test pass, therefore making sure that there is no creep.
- With adding of unit tests, the code should be more stable and less prone to bugs.
- This can be shown by research done by Microsoft entitled Realizing quality improvement through test driven development: results and experiences of four industrial teams. An except from the conclusion of this paper: “Our experiences and distilled lessons learned, all point to the fact that TDD seems to be applicable in various domains and can significantly reduce the defect density of developed software without significant productivity reduction of the development team. Additionally, since an important aspect of TDD is the creation of test assets—unit, functional, and integration tests. Future releases of these products, as they continue using TDD, will also experience low defect densities due to the use of these test assets”. This paper can be found here.
- By using continuous integration and running unit tests when code changes are made, the unit tests can make sure that the changes are not damaging existing functionality.
- Unit tests can often show that a method or a class has too much responsibility. This can be seen if it is not clear what the output of a method is or if there is a too many inputs or if a lot is changed inside the method. Unit tests help the developer to think about how the code they are about to write will pass the test and only the test.
Example of Test Driven Design
In this example, I am going to create a Fibonacci Sequence. To start off, I created a project and a test project.
These are the steps I used in my TDD:
In the Test
project, I created a class called FibonacciSequenceTests
. In the class, I created the following test:
public void Test_Find_Fibonacci_Sequence_At_Position_Correct()
{
FibonacciSequence fibonacciSequence = new FibonacciSequence();
int sequenceAtPosition = fibonacciSequence.FindFibonacciSequenceAtPosition(8);
Assert.AreEqual(sequenceAtPosition, 21);
}
This obviously failed since there is no functionality in the solution, so I created the class:
public class FibonacciSequence
{
public int FindFibonacciSequenceAtPosition(int n)
{
int a = 0;
int b = 1;
for (int i = 0; i < n; i++)
{
int temp = a;
a = b;
b = temp + b;
}
return a;
}
}
<a href="http://anyurl.com" rel="tag" style="display: none; ">CodeProject</a>