TDD

3 Examples of TDD in .NET C#

In this article, we are going to give a brief introduction to the Test Driven Development (TDD) methodology. We will see how TDD works and how it is applied in software development, as well as its advantages and disadvantages. We’ll also show 3 practical examples of TDD in C# .NET so you can see how it’s applied in practice. If you’re interested in learning more about TDD and how it can help you develop high-quality software, don’t miss this article.

Introduction to TDD (Test Driven Development)

What’s TDD?

TDD is a software development methodology that involves first writing automated tests for a feature before writing the code for the feature. The objective of TDD is to ensure that the code meets the requirements specified in tests and to detect bugs and defects early.

TDD pros and cons

One of the main advantages of TDD is that it helps to develop clean and well-designed code. By writing tests before code, you think about how the functionality should be used and design your code in a way that is easy to test. In addition, by having automated tests, errors and defects in the code can be detected early, which makes it easier to correct them.

TDD also helps improve code quality by making it easier to refactor. By having automated tests, you can make code changes with confidence, knowing that the tests will detect if any existing functionality is broken.

However, TDD can be tedious and require more time to write automated tests for every small change in the code. Also, it can be difficult to write tests for complex functionality. Therefore, it is recommended to use TDD only for critical functionality and to use other testing techniques for less critical functionality.

In general, TDD is a useful software development approach for improving code quality, detecting bugs early, and facilitating the refactoring process. However, it takes a bit more time and effort to write automated tests.

«Red, Green, Refactor»

TDD is divided into three steps:

  • write an automated test,
  • write the code needed to make the test pass
  • and refactor the code to improve its quality.

This cycle is repeated until all the required features are implemented and pass the tests.

The TDD cycle is known as “Red, Green, Refactor” because of the colors used to indicate the status of tests. The loop is started by writing an automated test for a specific functionality. In this step, the test is expected to fail ( red ) since the functionality has not been coded yet.
After the test is written, the code necessary to make the test pass is written. In this step, the code is expected to pass the test ( green ) and is verified that it meets the requirements specified in the test.
Finally, the code is refactored to improve its quality, ensuring that the tests continue to pass ( Refactor). The cycle is repeated until all the required functionalities are implemented and pass the tests.
In short “Green, Red, Refactor” represents the sequence of first writing an automated test, then writing the code needed to make the test pass, and finally refactoring the code to improve its quality and ensure that the tests continue to pass.

When to use TDD?

There are some situations where it is highly recommended to use TDD:

  1. Projects with changing requirements : TDD helps to detect changes in requirements early and to ensure that the code continues to comply with them.
  2. Projects with a high risk of failures : TDD helps to detect and correct errors and defects early, which reduces the risk of system failures.
  3. Projects with a high degree of complexity : TDD helps ensure that code is being built in an incremental and controllable manner, making it easier to manage complex projects.
  4. Projects with a decentralized team : TDD helps ensure that all team members understand the requirements and that the code complies with them.

In general, TDD is a methodology that can be beneficial for any software development project, but it is especially useful in projects with a high risk of failure, a high degree of complexity, or a decentralized team.

Let’s go to code👇

Now that we know TDD a bit better, we’ll show you 3 practical examples in C# .NET so you can see how it’s applied in practice.

If your coffee is ready let’s get started.☕

First basic example of TDD in C# .NET

Here’s a basic TDD example in C# .NET:

1. Write an automated test for a specific functionality. For example, if we want to test a summation class, we could write a test like this:

[Fact]
public void Sum_TwoNumbers_ReturnsSum()
{
    // Arrange
    var calculator = new Calculator();
    var a = 2;
    var b = 3;
    var expected = 5;

    // Act
    var result = calculator.Sum(a, b);

    // Assert
    Assert.Equal(expected, result);
}

2. Write the code necessary to make the test pass. In this example, we would write the implementation of the Calculator class:

public class Calculator
{
    public int Sum(int a, int b)
    {
        return a + b;
    }
}

3. Run the tests and make sure they pass. In this case, the test should pass since the sum of 2 and 3 is 5.

4. Refactor the code to improve its quality, ensuring that the tests continue to pass.

It is important to mention that this is just a simple example and in practice automated tests can be more complex, but the process is the same, writing the automated tests first, then writing the code and finally refactoring the code to improve its quality and ensure keep the tests going.

Second TDD example in C# .NET

Here I show you an example of TDD in C# .NET of a functionality of a class of a web application:

1. Write an automated test for a specific functionality. For example, if we want to test a class that validates if a user is an administrator or not. We could write a test like this:

[Fact]
public void IsAdmin_AdminUser_ReturnsTrue()
{
    // Arrange
    var user = new User { Role = "admin" };
    var validator = new UserValidator();

    // Act
    var result = validator.IsAdmin(user);

    // Assert
    Assert.True(result);
}

2. Write the code necessary to make the test pass. In this example, we would write the implementation of the UserValidator class:

public class UserValidator
{
    public bool IsAdmin(User user)
    {
        return user.Role == "admin";
    }
}

3. Run the tests and make sure they pass. In this case, the test should pass since the user has an administrator role.

4. Refactor the code to improve its quality, ensuring that the tests continue to pass.

In this example you can see how it is verifying if a user is an administrator or not, through the UserValidator class, more tests can be added for different cases and scenarios, such as a user that is not an administrator or a user that does not exist.

In this way, functionalities and automated tests can be added for each of them, guaranteeing that the code meets the specified requirements and detecting errors and defects early.

Third example of TDD in C# .NET

Here’s a more complex C# .NET TDD example of a web application feature:

1. Write an automated test for a specific functionality. For example, if we want to test a class that performs a search in a database and returns a result set. We could write a test like this:

[Fact]
public void Search_ValidTerm_ReturnsResults()
{
    // Arrange
    var context = new SampleDbContext();
    var searcher = new DataSearcher(context);
    var term = "example";
    var expectedResults = new List<Customer> { new Customer { Name = "example 1" }, new Customer { Name = "example 2" } };

    // Act
    var results = searcher.Search(term);

    // Assert
    Assert.All(expectedResults, (expected, i) => Assert.Equal(expected.Name, results[i].Name));
}

2. Write the code necessary to make the test pass. In this example, we would write the implementation of the DataSearcher class:

public class SampleDbContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=testDB;Trusted_Connection=True;");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Customer>().HasData(
            new Customer { Name = "example 1" },
            new Customer { Name = "example 2" }
        );
    }
}

public class Customer
{
    [Key]
    public string? Name { get; set; }
}

3. Run the tests and make sure they pass. In this case, the test should pass as the search returns the expected results.

4. Refactor the code to improve its quality, ensuring that the tests continue to pass. In this example, things like how the database lookup is performed could be refactored to improve performance, or additional validations could be added to the code to ensure that the necessary parameters are being received.

This example illustrates how TDD can be applied to functionality that involves database access and data processing. It is important to mention that in this case a connection to a database is being simulated, but in a real application the necessary libraries and connections would be used to access a database.

In this way, functionalities and automated tests can be added for each of them, guaranteeing that the code meets the specified requirements and detecting errors and defects early.

Conclusion

In short, TDD will help us ensure code meets specific requirements and detect bugs and defects early by writing automated tests before writing system code. It is especially useful in projects with high risk of failure, complexity and decentralized teams.

That’s it, for now, I’ll keep updating and adding content in this post. I hope you found it interesting😉

Sample code

Samples TDD .NET / C#