Think with Enlab

Diving deep into the ocean of technology

Stay Connected. No spam!

How to apply Test-Driven Development with practical examples

 

Over the last few years, test-driven development (a.k.a. TDD) has grown in popularity. Many programmers have tried and failed with this technique, concluding that TDD is not worth the effort. Yet, in this article, we will go through what test-driven development is, explore why people use it, and reach out how to apply TDD effectively with practical examples.


Basic concept

What is Test-Driven Development?

Test-driven development is an approach in software development in which you write tests first before implementing a feature. Kent Beck created it in the late 1990s as part of Extreme Programming. It reverses the traditional technique that leads the software development by designing the code architecture first, writing code, then iteratively doing testing until it passes all the test cases that are defined after the development has been done. In TDD, we will focus on ‘what’ to implement first, instead of ‘how’ to achieve it. TDD focuses on code design and pursues a better quality of code as well.

 

Advantages of using Test-Driven Development

  • TDD guarantees that all the codes are well tested. A key characteristic of a test is that it can fail, and the development team verifies that each new test fails. This will bring high confidence when we release the software to production.
  • Less code: You only need to write the minimum lines of code that pass the test. It will help to reduce code duplication, enabling faster innovation and continuous delivery.
  • Easier to maintain: TDD makes your code flexible and extensible. The code can be refactored or moved with minimal risk of breaking code. Since refactoring is an important step repeated in every iteration of the TDD cycle, the code will be continuously maintained.

 

Disadvantages of using Test-Driven Development

TDD also has some disadvantages that you need to consider depending on your project size.

  • It requires more time to write the test. Since the test should be pre-defined, it would take time for our developers to write test cases and so the development time can be longer than usual.
  • It requires developers more experienced. It means that developers working with TDD need to have an understanding of this technique, have enough skill to write failing tests, as well as the ability to follow the process strictly.

 

How to apply Test-Driven Development?

There are 3 steps to implementing TDD:

  • Step 1: Understand the requirement and write code that makes the test fail.
  • Step 2: Write code to make the test pass.
  • Step 3: Refactor the code you have just written to make it more readable and maintainable.

These 3 steps are often described as a Red-Green-Refactor (RGR) cycle as in the figure below.

Test driven development cycle

 

The purpose of the RGR cycle is to guarantee that you only write sufficient code to make the test pass, and meanwhile keep the code well structured. To ensure that, Robert C. Martin defines the three laws of TDD:

“First law: You may not write production code until you have written a failing unit test.
Second law: You may not write more of a unit test that is sufficient to fail, and not compiling is failing.
Third law: You may not write more production code that is sufficient to pass the currently failing test.”

 

TDD implementation in a .NET Core application with code examples

Let us take an example of how TDD is applied in a project. From Visual Studio, create a .NET Core Web API named TddExample. By default, it will create an empty solution with a default controller named “WeatherForecastController”. But if it did not create due to an older version of Visual Studio, let us create it manually and replace it with the following code.

WeatherForecastController:



 namespace TddExample.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly List<WeatherForecast> Data = new List<WeatherForecast>
        {
            new WeatherForecast(DateTime.Now, 16, "Freezing"),
            new WeatherForecast(DateTime.Now.AddDays(1), 20, "Cold"),
            new WeatherForecast(DateTime.Now.AddDays(3), 21, "Cold"),
            new WeatherForecast(DateTime.Now.AddDays(3), 24, "Mild"),
            new WeatherForecast(DateTime.Now.AddDays(4), 38, "Sweltering"),
            new WeatherForecast(DateTime.Now.AddDays(5), 39, "Scorching"),
            new WeatherForecast(DateTime.Now.AddDays(6), 40, "Scorching"),
            new WeatherForecast(DateTime.Now.AddDays(7), 26, "Mild"),
            new WeatherForecast(DateTime.Now.AddDays(8), 29, "Warm"),
            new WeatherForecast(DateTime.Now.AddDays(9), 30, "Hot"),
            new WeatherForecast(DateTime.Now.AddDays(10), 31, "Hot"),           
            new WeatherForecast(DateTime.Now.AddDays(11), 32, "Balmy"),
            new WeatherForecast(DateTime.Now.AddDays(12), 27, "Warm"),
            new WeatherForecast(DateTime.Now.AddDays(13), 22, "Mild"),
        };
      
        public WeatherForecastController()
        {
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            return null;
        }
    }
}
Code language: C# (cs)

 

and WeatherForecast class:

 namespace TddExample
{
    public class WeatherForecast
    {
        public DateTime Date { get; set; }

        public int TemperatureC { get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

        public string Summary { get; set; }

        public WeatherForecast(DateTime date, int tempC, string summary)
        {
            Date = date;
            TemperatureC = tempC;
            Summary = summary;
        }
    }
}Code language: C# (cs)

 

Assuming we have the following requirement for the HttpGet API to get a weather forecast:

  • The API should return the weather forecast information for the next seven days, including the current day.
  • If a day with the temperature in Celsius is greater than 39, the Summary should be added with the warning text: “Do not go outside at noon!”.

 

First, let’s add an NUnit Test project to the solution, named TddExample.Test. Rename UnitTest1.cs with the name WeatherForecastControllerTest.cs, and replace it with the following code.

 namespace TddExample.Test
{
    public class WeatherForecastControllerTest
    {
        [SetUp]
        public void Setup()
        {
        }
    }
}Code language: C# (cs)

 

Now everything is ready for the implementation of TDD. The first step of TDD is to write a failed test. Add the following method to the Test class we have just created above.

 [Test]
        public void Get()
        {
           // Arrange
            var controller = new Controllers.WeatherForecastController();

            // Act
            var nextSevenDaysForecast = controller.Get();

            // Assert
            // 1. The API returns enough 7 days data
            Assert.AreEqual(7, nextSevenDaysForecast.Count());
            // 2. Data is 7 days in-a-row from today
            int index = 0;
            for (DateTime date = DateTime.Now.Date; date <= DateTime.Now.AddDays(6); date = date.AddDays(1))
            {
                Assert.AreEqual(nextSevenDaysForecast.ElementAt(index).Date.Date, date.Date);
                index++;
            }
        }Code language: C# (cs)

 

Note: You can read more about Arrange-Act-Assert pattern.


At this moment, if we try to run the test, we will get the failed result with this error: System.ArgumentNullException. It is the correct result because the method Get() returns null, so the .Count() method raised an exception. We have done step 1: writing the failed test (the ‘Red’ step).

1. TDD example in .NET

 

Now, as we have the failed test, the next step is to make it ‘Green’ by implementing the method to make it pass the test. Edit the Get method in the Controller to make it returns the next seven days data.

 [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            return Data.Where(data => data.Date < DateTime.Now.AddDays(6));
        }Code language: C# (cs)

 

As we have implemented the code in order to make the test pass, let’s re-run the test to check the result. You might beware that it is still failed.

2. Example of test driven development

By looking at the Error Message, Expected: 2022-05-27 00:00:00 But was: 2022-05-26 00:00:00, we know one of our Assert conditions has not passed. Re-check the code implementation to find what causes the test to be failed, and correct it. After a few seconds, you will see that it is because the data is incorrect: the 3rd day in our mock Data should be the next two days, not three days.

3. Example of TDD

 

Edit it to AddDays(2) and re-run to see if the test is passed now.

4. Test driven development example

 

Now you realize that TDD will help us ensure the code's accuracy. It will ensure the program runs correctly as long as all the tests are well-defined, possible falling cases are covered, and all tests are passed after implementation.

Continue with the second requirement, let’s repeat the RGR cycle by defining a second test method to check if the API returns the correct data that matches the requirement:

 [Test]
        public void GetWithWarning()
        {
            // Arrange
            var controller = new Controllers.WeatherForecastController();

            // Act
            var nextSevenDaysForecast = controller.Get();

            // Assert
            if(nextSevenDaysForecast.Any(_ => _.TemperatureC > 39))
            {
                const string Warning = "Do not go outside at noon!";
                Assert.IsTrue(nextSevenDaysForecast
                    .Where(_ => _.TemperatureC > 39)
                    .All(data => data.Summary
                    .EndsWith(Warning)));
            }
        }Code language: C# (cs)

 

And like what we did before, first re-run to see if it’s failed.

5. Code sample of test driven development

Then, implement the code in the Controller to get it passed.

 [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var nextSevenDaysForecast = Data.Where(data => data.Date < DateTime.Now.AddDays(6));

            const string Warning = "Do not go outside at noon!";
            nextSevenDaysForecast.Where(_ => _.TemperatureC > 39).ToList().ForEach(date =>
            {
                date.Summary = string.Concat(date.Summary, Warning);
            });

            return nextSevenDaysForecast;
        }Code language: C# (cs)

 

Re-run to see if the 2 tests are passed.

6. Result of code sample TDD

 

Although the code in this example is simple enough that the Refactor phase is not required, in production, you should be aware of refactoring continuously after a test has passed to keep the code well-structured and maintainable.

 

Final thoughts
Test-driven development is a process of developing, running automated tests, and refactoring before the actual development of the application. It enables developers to build solid and robust software with clearer and more understandable code. Hopefully, you have an overview of TDD and know how to apply it to your software development practice.

Happy coding!

 

CTA Enlab Software

About the author

Tuan Do

Hi, I'm Tuan Do. As a software engineer at Enlab, I love coding and developing software that brings value to our clients. I specialize in back-end technologies such as .NET, SQL Server, and MongoDB. I’m also a fan of cloud computing platforms like AWS. When not at work, I enjoy reading books and doing technical research.

Up Next

How to apply SOLID principles with practical examples in C#
September 07,2021 by Chuong Tran
In Object-Oriented Programming (OOP), SOLID is one of the most popular sets of design principles...
Domain-Driven Design in ASP.NET Core applications
May 31,2021 by Loc Nguyen
The technology industry has been thriving for the second half of the last century. It's...
How to create real time chat applications using WebSocket APIs in API Gateway
March 26,2021 by Trong Pham
My experience developing real-time messaging applications leveraging AWS services inspires me to share with the...
How to build and deploy a 3-tier architecture application with C#
March 11,2021 by Uyen Luu
Hi, back to the architecture patterns, in the last article I explained what the three-layer...
Roll to Top

Can we send you our next blog posts? Only the best stuffs.

Subscribe