DEV Community

Cover image for Clean Code in C# Part 7 Unit Tests
Caio Cesar
Caio Cesar

Posted on

Clean Code in C# Part 7 Unit Tests

Introduction

Units tests are an efficient practice that can improve code quality in the software development process. When creating unit tests one should focus on testing small sections of code, this includes testing behavior, and its execution for when these methods executes successfully and when they fail.

Test Driven Development (TDD)

When developing TDD applications, one must first create unit tests then develop code to cover the tests. According to uncle bob you must consider 3 rules when working with TDD:

  1. Don't develop production code unless it makes a failing unit test pass.
  2. Don't develop production code when there is more than one unit test failing, code that doesn't compile is failing.
  3. Don't develop production code that doesn't pass a failing unit test.

When following the rules above the unit test in a application should increase as code grows. With .NET there are resources that could help developers check for code coverage as seen in the Introduction to Software Testing on Visual Studio post.

Clean Tests

Writing tests that pass and fail is not enough in modern software development environments. As stated before tests will grow as production code grows. The rules and complexity of applications could mean writing more tests to verify every scenario. However, good code coverage with sloppy test is not the way to go.

As requirements changes through the advent of time, production code needs to be updated and maintained. This means refactoring existing tests. The worst the tests are written the harder it is to make changes to them. Maintaining tests should not be a problem when they are well written. Keep in mind that The code written for tests are as important as production code, they should be carefully written and planned.

When creating unit tests readability should be a top priority. Analyze the following test created with xUnit.net:

Example 1:

 public class UserTest
 {
      [Fact]
      public void UserNamePass()
      {
          string fn = "Caio";
          string ln = "Sousa";

          Assert.Equal(ln + ", " + fn, Username.Format(fn, ln));
      }

      [Fact]
      public void UserNameFail()
      {
         string fn = "Caio";
         string ln = "Sousa";

         Assert.NotEqual(ln + " " + fn , Username.Format(fn,ln));
      }
 }
Enter fullscreen mode Exit fullscreen mode

The unit tests in Example 1 calls a static method from the Username class that formats a last and first name. Even though it is a simple method, there are many ways to make this test class cleaner. Now analyze the refactored test class:

Example 2:

public class UserNameTest
{
    [Fact]
    public void UserName_Format_Must_Pass()
    {
        string firstName = "Caio";
        string lastName = "Sousa";
        string expected = "Sousa, Caio";

        string result = UserName.Format(firstName, lastName);

        Assert.Equal(expected, result);
    }

    [Fact]
    public void UserName_Format_Must_Fail()
    {
        string firstName = "Caio";
        string lastName = "Sousa";
        string expected = "Sousa Caio";

        string result = UserName.Format(firstName, lastName);

        Assert.NotEqual(expected, result);
    }
}

Enter fullscreen mode Exit fullscreen mode

In Example 2, the test class name has been changed, the methods are more explicit, the variable names has been updated. At this point, developers should be more confident to make changes to the Format method. The test methods are also divided into three important sections: 1) Arrange - Produce test data; 2) Act- Operates with arranged data; 3) Assert- Validates if the results are as expected.

Each test method tests only one concept, in this case the formatting. You should avoid long functions that tests multiple concepts. In Example 2 we could also test for null exceptions and this would be a new method that would be called UserName_Format_Must_Throw_ArgumentNullException.

Conclusion

Software testing goes beyond what was covered in this post. The .NET environment provides resources that facilitates software testing within the Visual Studio IDE. Other libraries for mocking and for fixtures could also be explored and serve as an asset. Other strategies like Behavior Driven Development (BDD), Integration Testing, Automation Testing and Load Testing are also valuable resources.

A project that has clean tests with decent test coverage, gives me confidence to make changes to code. Therefore make sure your code is clean and your tests are also clean.

References

  1. Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin.
  2. Unit test basics
  3. xUnit.net

Discussion (0)