Photo by Sung Jin Cho on Unsplash
Red Light. Green Light. Refactor: Test Driven Development
What Am I On About?
Let's get straight to the point. Test Driven Development or TDD for short, is an approach to software development where unit tests drive the design and development of the code; with TDD, tests are written and implemented before any code is written. We're going to focus on a TDD approach called "Red, Green, Refactor".
Red, Green, Refactor
There are three stages of development for this TDD approach. Can you guess what they are?
Well Done! Give yourself a gold star! ⭐
They are, of course, "Red", "Green" and then "Refactor"
Red
Let's firstly look at "Red" and what this means. The "Red" stage is always your starting point in this approach of TDD and this is where you think about what you want to develop. The purpose of this phase is to define what you want to implement but not writing any logic to make the feature actually work.
Time For Some Code
public interface ICalculateAreas
{
}
public class AreaCalculator : ICalculateAreas
{
}
[TestClass]
public class TestDrivenDevelopmentExample
{
private ICalculateAreas _areaCalculator;
[TestInitialize]
public void Setup()
{
_areaCalculator = new AreaCalculator();
}
[TestMethod]
public void TestDrivenDevelopmentExample_AreaCalculator_CalculateQuadrilaterals()
{
var expected = 24;
var actual = _areaCalculator.CalculateQuadrilaterals();
Assert.AreEqual(expected, actual);
}
}
So in the code snippet above, we have a C# interface, class and test class. The interface and class are empty and purposely don't contain any methods or logic; this is to satisfy the Red stage.
Before we even run this in Visual Studio we will get an error that looks something like this: Have you ever been so happy to see a failing test or an error in your code?
Brilliant! We have completed the Red stage!
Green
Time for "Green" and you guessed it... that means we're going to make this test pass BUT we are going to do the very minimum to get this test to build & pass; we'll think about making the method clean, readable and efficient later.
In our example above, we want our CalculateQuadrilaterals() method to return the same value as our expected variable, in this case, that's 24. So, what's the first thing we need to do to get this thing building & passing?
...
Need another minute?
...
Correct! We need to create the method
Firstly, we need to create the method to please Visual Studio. Let's go ahead and do that; we need to add the method to our interface and then to our class.
public interface ICalculateArea
{
decimal CalculateQuadrilaterals();
}
public class AreaCalculator : ICalculateArea
{
public decimal CalculateQuadrilaterals()
{
throw new System.NotImplementedException();
}
}
Done! Visual Studio is happy but our test is failing. Let's make it pass. We'll add in a couple of hardcoded values so that the method returns our expected value.
public class AreaCalculator : ICalculateArea
{
public decimal CalculateQuadrilaterals()
{
var height = 4;
var width = 6;
return height * width;
}
}
OMG! It's passed!
Refactor
Congratulations, we've written some code that not only builds but it also passes! However... there is a slight problem with our code. What about quadrilaterals that have an area of anything BUT 24? We've hardcoded in the values to return the arbitrary value of 24. What if the height was 14 and the width was 26? Would our method be able to calculate that? Nah, we're always going to get 24. Let's change that by refactoring our code. That's right, in the Refactor stage we're going to refactor our code... who'd of thought it? This stage is important as we want to refactor our code; thinking of performance, readability amongst other things, but ensuring that our test still passes.
For our code we want the height and width to be passed into our method and remove the hardcoded values. We'll add parameters into our method, remember to add the params into the interface as well.
public interface ICalculateArea
{
decimal CalculateQuadrilaterals(decimal height, decimal width);
}
public class AreaCalculator : ICalculateArea
{
public decimal CalculateQuadrilaterals(decimal height, decimal width)
{
return height * width;
}
}
[TestClass]
public class TestDrivenDevelopmentExample
{
private ICalculateArea _areaCalculator;
[TestInitialize]
public void Setup()
{
_areaCalculator = new AreaCalculator();
}
[TestMethod]
public void TestDrivenDevelopmentExample_AreaCalculator_CalculateQuadrilaterals()
{
var height = 4;
var width = 6;
var expected = 24;
var actual = _areaCalculator.CalculateQuadrilaterals(height, width);
Assert.AreEqual(expected, actual);
}
}
We've added parameters into our method for the height and width of the quadrilateral we are trying to calculate the area for; we've removed the hardcoded height and width variable; we've added height and width variables into our unit test and passed them into the CalculateQuadrilaterals method.
Moment of truth... does our test still pass?
SUCCESS!
We've done it! We've gone through all the stages of Red, Green, Refactor. This is obviously a very simple example of using Red, Green, Refactor but no matter how complex the feature is, the stages and principles remain the same.