facebook

Blog

Stay updated

Let’s learn how to use the TDD method to improve the approach to our applications’ development
TDD: the whole code is guilty, until proven innocent!
Wednesday, May 15, 2019

While I was working on a client’s project with one of our partners, I had the opportunity to face the development of some tasks, using a method I’ve heard about many times, but that I have never used before: the Test Driven Development (TDD). _This method consists in writing tests of the code we are developing, before we write the code itself!
Since it is difficult today to find projects, for whom it is easy to have some tests’ arrays, this experience really strikes me, mostly due to the added value that this method can give to software development.

The principles of this protocol have been defined in detail by Uncle Bob and you can find them below:

  • You are not allowed to write any production code unless it is to make a failing unit test pass.
  • You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  • You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

In brief, what you have to keep on mind as you are using this method is:

“Only ever write code to fix a failing test”


TDD three stages: Red, Green Refactor

The Test Driven Development founds itself on three stages: Red, Green, Refactor.

Red stage

In this stage, you have to write a behavior you would like to test, but you don’t need to think about how the code should be implemented: you just need to identify how it must acts with specific inputs.
If you are thinking about how to implement it, you’re wrong!
In this stage, you only need to decide “how” your code will be used.

Green Stage

This is the stage we programmers mostly like: writing code.
The most common mistake one can do in this stage is to think to implement the test’s expected behavior in the best possible way, even getting it complicated for future developments.
In this case too, you’re wrong!
You just need to write enough code to let the test passing from red to green, but you are not required to think if you are following the Best Practices, and so on.
The Test Driving Development offers to-do list where you can take note of all the passage you need to complete the functionality you are implementing. The same list contains also doubts or problems you discover during the code’s writing. At the end of the process, this list must be empty.

Refactoring stage

The last stage of the process has a precise purpose: improve the code, without changing its behavior.
We have our “green test”, which grant us that the implemented functionality will do what it should: we just need to improve the code.
It’s time to delete the unnecessary code, look at what we wrote critically and apply best practices.
As soon as we complete this stage, we are ready to start again with the red stage.

The funny part

Now that the operational principles are clear, I would like to apply this method to an easy example. Create a Web API application in .NET Core, which only function is to accept a GET request with a custom header and return a 202 (Accepted) If the customer header is correct, or a 401 (Unauthorized) if it is incorrect or absent.
I use VisualStudio templates to be fast as possible with the creation of a new solution.

I add one more project to the solution to manage the test.

In this way, I have my Solution with two projects inside it: one is for tests and the other one is for the API we would create.

We need to tidy up of classes created in the template: let’s delete ValueController.cs and UnitTest1.cs

We create the classes DemoControllerTest in the test project, and DemoController in the API project.

using Microsoft.VisualStudio.TestTools.UnitTesting;
 
namespace TddStepToStep.Tests
{
    [TestClass]
    public class DemoControllerTest
    {
    }
}
using Microsoft.AspNetCore.Mvc;
 
namespace TddStepToStep.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class DemoController : ControllerBase
    {   
    }
}

Specify the test target, that’s to say the class DemoController

private DemoController _target;

Add a method to initialize the test class, where we create the DemoController request, by giving it a configuration class. In the configuration class there is the ApiKey property and it will be used to establish if the value passed to the request is correct.
The compiler will surely advise us that there’s no DemoControllerConfig class, that DemoController has no constructor, which can accept that kind of parameter and that it absolutely doesn’t know what is CORRECT_API_KEY.

We can start from the easiest thing to do: create the constant CORRECT_API_KEY

public const string CORRECT_API_KEY = "1234";

We create then the class DemoControllerConfig in the API project,It is empty at the beginning, but VS will help us (through the shortcut CTRL + ‘.’ ) and will suggest us to add a property ApiKey to the class. We can do the same to create a constructor and its relative private property for DemoController:

using TddStepToStep.Controllers;
 
namespace TddStepToStep.Tests
{
    [TestClass]
    public class DemoControllerTest
    {
        public const string CORRECT_API_KEY = "1234";
        private DemoController _target;
 
        [TestInitialize]
        public void Init()
        {
            _target = new DemoController(new DemoControllerConfig() { ApiKey = CORRECT_API_KEY });
        }
    }
}
using Microsoft.AspNetCore.Mvc;
 
namespace TddStepToStep.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class DemoController : ControllerBase
    {
        private DemoControllerConfig demoControllerConfig;
 
        public DemoController(DemoControllerConfig demoControllerConfig)
        {
            this.demoControllerConfig = demoControllerConfig;
        }
    }
}

It’s normal to pass a class that contains the ApiKey to Controller, instead to recover the value from a configuration file, when you use the “test first” method: we are dividing our logic from external factors.
The next step is to create the real test.
I want to test that the Status Code of the answer is 202 accepted , when an HTTP request with an header with the same value of the provide one, arrives.
The test’s name must denote exactly what we want to test. A good method to write the test’s name is to follow this rule: Should_ExpectedBehavior_When_StateUnderTest.
The test name would be then: ShouldReturnAcceptedResultWhenCorrectApiKeyIsPassed.
To pass a Header to a Controller, we need to write some code.

_target.ControllerContext = new ControllerContext();
_target.ControllerContext.HttpContext = new DefaultHttpContext();
_target.ControllerContext.HttpContext.Request.Headers.Add("X-API-KEY", CORRECT_API_KEY);

To add a Header, we create a new ControllerContext for the Controller we are going to test, to which we set up a new HttpContext that allows us to access to the Request. In the Request, we find the Headers. We add then the Header X-API-KEY with the correct value. Now we can simulate the API call, simply invoking the method that answers to the call.

var resp = _target.GetValues();

we transmit the obtained answer to the Assert.

Assert.IsInstanceOfType(resp, typeof(AcceptedResult));

The compilator will complains about the non-existance of GetValues(), but we can use the command CTRL + ‘.’ to create the lacking method we are going to implement.

[HttpGet]
public ActionResult GetValues()
{
    throw new NotImplementedException();
}

We finally have our test’s method, which we can execute and that we expect to fail (red stage):

[TestMethod]
public void ShouldReturnAcceptedResultWhenCorrectApiKeyIsPassed()
{
    _target.ControllerContext = new ControllerContext();
    _target.ControllerContext.HttpContext = new DefaultHttpContext();
    _target.ControllerContext.HttpContext.Request.Headers.Add("X-API-KEY", CORRECT_API_KEY);
    var resp = _target.GetValues();
    Assert.IsInstanceOfType(resp, typeof(AcceptedResult));
}

The test fails because we still don’t implement the GetValues method in the controller.
We will then implement the method, in order to let it changes to green stage.

[HttpGet]
public ActionResult GetValues()
{
    if (Request.Headers.ContainsKey("X-API-KEY") 
            && Request.Headers["X-API-KEY"].Equals(demoControllerConfig.ApiKey))
        return Accepted();
 
    return Ok();
}

Run the test again:

We respect the first requirement, now we need to obtain a 401 in the event that the key was incorrect.
I add the test and I run it, but I await that it will fails again.

public const string WRONG_API_KEY = "1235";
[TestMethod]
public void ShouldReturnUnauthorizedResultWhenWrongApiKeyIsPassed()
{
    _target.ControllerContext = new ControllerContext();
    _target.ControllerContext.HttpContext = new DefaultHttpContext();
    _target.ControllerContext.HttpContext.Request.Headers.Add("X-API-KEY", WRONG_API_KEY);
    var resp = _target.GetValues();
    Assert.IsInstanceOfType(resp, typeof(UnauthorizedResult));
}

We receive and error message because the test expects a “Unauthorized”, but it receives an OK.
We change the behavior with the method GetValues:

[HttpGet]
public ActionResult GetValues()
{
    if (!Request.Headers.ContainsKey("X-API-KEY") 
            || !Request.Headers["X-API-KEY"].Equals(demoControllerConfig.ApiKey))
        return Unauthorized();
 
    return Accepted();
}

Run the test again:

great! We reach the goal

It still lacks the “refactoring stage”: you can do it on your own as exercise! You can, for example, us a part of the code in an Action Filter…

I wish I was able to intrigue you
See you next