Blog for Junior Developers C#/.NET

Monday, July 08, 2024

Test Driven Development, abbreviated as TDD, i.e. test-driven software development. That is, as if the tests drive our code that we will write. I think TDD has been heard by every programmer, but few of them have ever tried to write software using this approach. To start writing according to TDD, you first need to learn how to write unit tests. If you haven't written any tests before, try reading the article about unit tests first and then come back to this topic :) There are two techniques for writing unit tests. You can write code first and then tests in parallel with that code, or you can also write the test first and then the code. This second technique is Test Driven Development.

test-driven-development-benefits-of-using-tdd-on-an-example-in-dotnet.jpg

Advantages of using TDD


We focus on better quality code. Thanks to the fact that the test was written first, we are sure that the code will be testable :) Therefore, it will follow good programming practices. Thanks to the refactoring required in each cycle, we will be able to improve it on an ongoing basis.

If we first write code and then implement tests into this code, we usually skip the red phase, i.e. we do not have tests that fail. A good practice is then, after adding a test, to temporarily comment out the line of code that we are testing. If after commenting on this fragment, the test does not pass, it means that we are testing the correct thing. Then you need to uncomment this piece of code. When using TDD, you can skip this stage, writing according to the TDD cycle, you can always be sure that you are testing the right thing.

Thanks to TDD, we have a broader look at the code before moving on to the actual implementation. We focus on thoroughly understanding all requirements. Thanks to this, we are able to predict more test cases that need to be checked.

Unfortunately, we will not avoid all errors, but there will certainly be fewer of them in our production code, and we will also shorten the total software development time.


TDD cycle


cykl-tdd-red-green-reafctor.png

The TDD approach consists of three stages called Red-Green-Refactor.
We start implementations from the red phase, this is the phase in which we write the first test and its result should be red, i.e. the test should not pass. Additionally, often even after this phase, the code will not compile because you may be using classes in the test that do not exist yet.
The second stage, green, is implementing the code for our test. Here it is important to note that we are implementing the simplest code that meets the requirements of our current test. After implementing the code, the test should be positive, i.e. green.
The final stage is refactoring the code, or tests, so that the code remains green. You must remember this stage so that the code is of the highest quality possible.
Then these cycles are repeated to implement subsequent functions in our application.


Example


Let's return to the popular FizzBuzz algorithm, for which we wrote unit tests in one of the previous articles. This time, let's try to write unit tests using the TDD method, i.e. we will start with a test. As a reminder, such a FizzBuzz algorithm must meet the following rules:
-If the argument is divisible by 3, returns Fizz.
-If the argument is divisible by 5, returns Buzz.
-If the argument is divisible by 3 and 5, returns FizzBuzz.
-If the argument is not divisible by 3 or 5, returns the value of the argument.


Implementation in accordance with TDD


We start with the red phase, i.e. by writing a unit test first, it may look like this:

[Test]
public void GetOutput_WhenInputIsDivisibleOnlyBy3_ShouldReturnFizz()
{
    var fizzbuzz = new FizzBuzz();

    var result = fizzbuzz.GetOutput(3);

    Assert.That(result, Is.EqualTo("Fizz"));
}


 At this point we get a compilation error because the FizzBuzz class does not exist yet. We move to the next green phase, after which the test should be green. I am creating a FizzBuzz class. Then the GetOutput method, which returns a string and takes an int argument. Adds the simplest possible implementation to make the test green. This is what my method looks like:

public string GetOutput(int number)
{
    return "Fizz";
}


 So at the moment this is the simplest implementation that meets my tests, we move on to the next stage of refactor. At this point, the code is quite simple, we don't need to refactor anything here. We have completed our first TDD cycle. We move on to the next cycle. We add a new test, which will be red first. This is what the test looks like in which we check our second assumption, i.e. the argument divisible by 5 should return Buzz.

[Test]
public void GetOutput_WhenInputIsDivisibleOnlyBy5_ShouldReturnBuzz()
{
    var fizzbuzz = new FizzBuzz();

    var result = fizzbuzz.GetOutput(5);

    Assert.That(result, Is.EqualTo("Buzz"));
}


The next stage is green, we add the simplest code that will meet these requirements.

public string GetOutput(int number)
{
    if (number % 5 == 0)
        return "Buzz";
    else
        return "Fizz";
}


The code meets the requirements, we move on to the next stage of refactoring. We can improve this code a bit at this stage. We can get rid of else from this condition.

public string GetOutput(int number)
{
    if (number % 5 == 0)
        return "Buzz";

    return "Fizz";
}


 We start another TDD cycle, add another test, start over again.

[Test]
public void GetOutput_WhenInputIsDivisibleBy3And5_ShouldReturnFizzBuzz()
{
    var fizzbuzz = new FizzBuzz();

    var result = fizzbuzz.GetOutput(15);

    Assert.That(result, Is.EqualTo("FizzBuzz"));
}


 Then we add the simplest implementation.

public string GetOutput(int number)
{
    if ((number % 3 == 0) && (number % 5 == 0))
        return "FizzBuzz";

    if (number % 5 == 0)
        return "Buzz";

    return "Fizz";
}


We don't have anything to refactor, we add another last test from our requirements, i.e. if the argument is neither divisible by 3 nor 5, we return the argument value. First a test.

[Test]
public void GetOutput_WhenInputIsNotDivisibleBy3Or5_ShouldReturnInput()
{
    var fizzbuzz = new FizzBuzz();

    var result = fizzbuzz.GetOutput(1);

    Assert.That(result, Is.EqualTo("1"));
}


 Then code that will pass all the tests.

public string GetOutput(int number)
{
    if ((number % 3 == 0) && (number % 5 == 0))
        return "FizzBuzz";

    if (number % 3 == 0)
        return "Fizz";

    if (number % 5 == 0)
        return "Buzz";

    return number.ToString();
}


This way, we managed to meet all the requirements. We implemented the FizzBuzz algorithm using TDD.


Unit Testing School


If you would like to learn how to write unit tests in C#/.NET applications, consider joining the Unit Testing School - [here].


SUMMARY:


Test Driven Development is an interesting software development technique that, when used well, will improve the quality of your code. If you are a beginner who doesn't write or doesn't know how to write unit tests yet, start with learning tests first, and only then I recommend you try writing according to TDD. If you have already mastered unit testing, I recommend you try it yourself, maybe use this technique on a smaller, side project at the beginning and see if it is suitable for you :)

Author of the article:
Kazimierz Szpin

KAZIMIERZ SZPIN
Software Developer C#/.NET, Freelancer. Specializes in ASP.NET Core, ASP.NET MVC, ASP.NET Web API, Blazor, WPF and Windows Forms.
Author of the blog CodeWithKazik.com

Previous article - Whether to Unit Test Private Methods - Examples in C#
Next article - We Test Database Operations - Introduction to Integration Testing in .NET
Comments (1)
Kazimierz Szpin
KAZIMIERZ SZPIN, poniedziałek, 15 lipca 2024 11:04
What do you think about this article? I'm curious about your opinion.
Dodaj komentarz
© Copyright 2024 CodeWithKazik.com. All rights reserved. Privacy policy.
Design by Code With Kazik and Modest Programmer.