TDD
Over the last few years I have really pushed teams I work in to use TDD as much as possible and find myself having similar conversations about it with other developers. I really enjoy passing on knowledge the same way I also enjoy learning from others. So, this blog (I hope) should detail TDD and how to use it for maximum benefit.
Test Driven Development (TDD) refers to the technique whereby the developers writes their unit tests first before writing the code that will actually "do" something. This means the developer can think about how the code should flow in normal circumstances, edge cases and on error. It also means that writing small, bite-size unit tests allow the developer to simply break the work down by working through their unit tests one at a time and get each one to pass.
(Please note that the example used is simple and just to illustrate, please do not assume this is a correct design or correct implementation of the class)
What does that actuallly mean in practice?
For the sake of this post, let's imagine the developer (me) is writing a simple calculator based on inputs.
Using TDD as an approach the first item to do is define the interface for the calculator (the design of the interface and the method can be saved for another discussion).
interface ICalculator
{
double Calculate(double firstValue, double secondValue, string operand);
}
The next step would be to write a unit test to cover each possible entry. Let's take a look at one......
[Test]
public void Given_Two_Doubles_And_StringValue_Of_Addition_Check_That_Calculator_Returns_Correct_Result()
{
var calc = new Calculator();
double one = 10.0;
double two = 15.75;
string operand = "+";
double result = calc.Calcuate(one, two, operand);
Assert.AreEqual(25.75, result);
}
Immediately, we will get a compilation error because the class "Calculator" does not exist. So we would write that class:
public class Calculator : ICalculator
{
double Calculate(double firstValue, double secondValue, string operand)
{
}
}
Now we would get another compilation error as the method Calculate does not return a value as expected. So the next step would be to add simple code to allow both a default result to return and to perform the addition expected from the first test.
public class Calculator : ICalculator
{
double Calculate(double firstValue, double secondValue, string operand)
{
if (operand == "+")
{
return firstValue + secondValue;
}
return 0; //default return value
}
}
(Note: Please don't use this code in practice, I am just demonstrating TDD - this is not the way to write a calculator program)
When we run the test, it should now pass. So the developer can continue to write all the tests and extend the class to allow for each test to pass. So the developer would write a test to cover subtraction, multiplication, division, all the above with whole numbers, all the above with simple decimal numbers etc.....
Eventually the developer will have to implement edge cases such as:
[ExpectedException]
[Test]
public void Given_Two_Doubles_And_A_Bad_StringValue_Of_Addition_Check_That_Calculator_ThrowsAnException()
{
var calc = new Calculator();
double one = 10.0;
double two = 15.75;
string operand = "@";
double result = calc.Calcuate(one, two, operand);
}
So therefore the class Calculator will have to throw an Exception and the developer will have to implement this somehow, otherwise the test will fail.
What TDD means, is that the developer is not over-awed and thinking too macro about building the whole calculator. But, instead, the developer is can simpy focus on developing small biteable chunks of code that meet a specific purpose (i.e a unit test). It also encourages developers to think about the code more upfront, and the design of it rather than code away first before realising they may have gone down the wrong path.
TDD is an excellent approach and I have encouraged developers to follow this, where appropriate. I have always added that caveat in because of course, there are times where it is just not practical. If you are building an enterprise wide system and you are following TDD strictly you would have to have your entire design done upfront. Sometimes that just isn't possible on a system that scale as it would take months to design it properly. But once some basic code is in place (covered by unit tests of course) TDD can be used to design and build a small sub system inside. TDD used properly and sensibly can actualy increase productivity and reduce bugs. I say that because ideally, developers would think about more than their normal scenario but also edge cases and exceptions.
TDD is also an excellent approach to identify a bug. For example, if a user has found an issue, the developer can write a test to reproduce that issue. Then the aim of the developer is to get the test to "pass" by finding and fixing the bug (maybe by trial and error). It also means that there would be at least one regression test to cover this bug should is regress from any future code change.
I hope this blog wasn't too long and illustrates TDD sensibly :-) Some of you may have noted that I have also employed BDD (Behaviour driven development), but that's a story for another day.....
Test Driven Development (TDD) refers to the technique whereby the developers writes their unit tests first before writing the code that will actually "do" something. This means the developer can think about how the code should flow in normal circumstances, edge cases and on error. It also means that writing small, bite-size unit tests allow the developer to simply break the work down by working through their unit tests one at a time and get each one to pass.
(Please note that the example used is simple and just to illustrate, please do not assume this is a correct design or correct implementation of the class)
What does that actuallly mean in practice?
For the sake of this post, let's imagine the developer (me) is writing a simple calculator based on inputs.
Using TDD as an approach the first item to do is define the interface for the calculator (the design of the interface and the method can be saved for another discussion).
interface ICalculator
{
double Calculate(double firstValue, double secondValue, string operand);
}
The next step would be to write a unit test to cover each possible entry. Let's take a look at one......
[Test]
public void Given_Two_Doubles_And_StringValue_Of_Addition_Check_That_Calculator_Returns_Correct_Result()
{
var calc = new Calculator();
double one = 10.0;
double two = 15.75;
string operand = "+";
double result = calc.Calcuate(one, two, operand);
Assert.AreEqual(25.75, result);
}
Immediately, we will get a compilation error because the class "Calculator" does not exist. So we would write that class:
public class Calculator : ICalculator
{
double Calculate(double firstValue, double secondValue, string operand)
{
}
}
Now we would get another compilation error as the method Calculate does not return a value as expected. So the next step would be to add simple code to allow both a default result to return and to perform the addition expected from the first test.
public class Calculator : ICalculator
{
double Calculate(double firstValue, double secondValue, string operand)
{
if (operand == "+")
{
return firstValue + secondValue;
}
return 0; //default return value
}
}
(Note: Please don't use this code in practice, I am just demonstrating TDD - this is not the way to write a calculator program)
When we run the test, it should now pass. So the developer can continue to write all the tests and extend the class to allow for each test to pass. So the developer would write a test to cover subtraction, multiplication, division, all the above with whole numbers, all the above with simple decimal numbers etc.....
Eventually the developer will have to implement edge cases such as:
[ExpectedException]
[Test]
public void Given_Two_Doubles_And_A_Bad_StringValue_Of_Addition_Check_That_Calculator_ThrowsAnException()
{
var calc = new Calculator();
double one = 10.0;
double two = 15.75;
string operand = "@";
double result = calc.Calcuate(one, two, operand);
}
So therefore the class Calculator will have to throw an Exception and the developer will have to implement this somehow, otherwise the test will fail.
What TDD means, is that the developer is not over-awed and thinking too macro about building the whole calculator. But, instead, the developer is can simpy focus on developing small biteable chunks of code that meet a specific purpose (i.e a unit test). It also encourages developers to think about the code more upfront, and the design of it rather than code away first before realising they may have gone down the wrong path.
TDD is an excellent approach and I have encouraged developers to follow this, where appropriate. I have always added that caveat in because of course, there are times where it is just not practical. If you are building an enterprise wide system and you are following TDD strictly you would have to have your entire design done upfront. Sometimes that just isn't possible on a system that scale as it would take months to design it properly. But once some basic code is in place (covered by unit tests of course) TDD can be used to design and build a small sub system inside. TDD used properly and sensibly can actualy increase productivity and reduce bugs. I say that because ideally, developers would think about more than their normal scenario but also edge cases and exceptions.
TDD is also an excellent approach to identify a bug. For example, if a user has found an issue, the developer can write a test to reproduce that issue. Then the aim of the developer is to get the test to "pass" by finding and fixing the bug (maybe by trial and error). It also means that there would be at least one regression test to cover this bug should is regress from any future code change.
I hope this blog wasn't too long and illustrates TDD sensibly :-) Some of you may have noted that I have also employed BDD (Behaviour driven development), but that's a story for another day.....
Comments
Post a Comment