Blog for Junior Developers C#/.NET

Saturday, July 06, 2024

In a previous blog post, I introduced you to automated testing. If you haven't read it yet, be sure to check it out before reading this article. Unit tests are one of the types of automatic tests. In this article, I will first tell you a little about unit tests in theory, tell you what good unit tests should be, and then we will write the first unit test, and then the next ones. Let's get started!

What are unit tests?


Let's start with the theory. Unit tests are small pieces of code that are used to test other code, i.e. individual units, i.e. classes and methods.

unit-tests-100-what-you-need-to-know-about-them.jpg

What should unit tests be like?


#1 They should be executed in isolation without external dependencies.
Unit tests are performed in isolation without any external dependencies such as a file, database or web service. Tests that use these dependencies are integration tests, which I will describe in more detail in future blog articles. Integration tests are often called unit tests by developers, but unit and integration tests are not the same.

#2 Unit tests should be repeatable and reliable.
Unit tests, because they are not dependent on any external dependencies, should always be repeatable. So the test result should always be the same. It doesn't matter when we run the unit test, in the morning or in the evening, on a different computer, a year after it was written, whether we run one unit test or all of them at once - the result should always be the same.

#3 Unit tests should be fast.
Unit tests are run many times precisely because they are fast. If your test takes a long time to execute, it means that it probably uses some external dependencies and is not a unit test.

#4 Unit tests should be easy, readable, if they fail, the error should be easy to find.
For unit tests, you should use appropriate naming conventions. Your tests should not contain too many lines of code and stick to the AAA (Arange, Act, Assert) principle. If the test fails, it should return an appropriate message telling you what went wrong.

#5 It is written using a unit testing framework.
There are many frameworks you can use. I suggest choosing one of two: NUnit or xUnit.net. Today's examples will be in NUnit, although xUnit.net is just as good. The only thing I advise against is using the MSTest framework, although it is available without adding additional frameworks, but it is a bit limited, that is, it lacks some interesting functions that are in the two previously mentioned ones.


First unit test in C#


Let's write our first unit code in C# using NUnit. Let's start with a simple example and try to write one test for the Add method in the Calculator class. Sample code for this method might look like this:

public class Calculator
{
    public int Add(int number1, int number2)
    {
        return number1 - number2; //intentional error
    }
}


#1 We are adding a new project with tests.

First, you need to add a new project to our solution. By convention, this could be a project called Calculations.UnitTests. This project will be our unit tests. It's best to separate unit tests from production code and integration tests.

#2 We add a reference to the tested project to the project with tests.
There are several ways to do this. For example, right-click on Calculations.UnitTests, then Add and Reference. After opening a new window, from the Project/Solution tab we select the tested project, in our case Calculations.

#3 Install NUnit.
We go to Manage NuGet Package and install it for the project with NUnit tests.

#4 We add a new class corresponding to the tested class.
In the test project, we add a new class that will be responsible for testing the Calculator class. By convention, we call it CalculatorTests. In this class, methods will be added to test the Calculator class. For each subsequent tested class, it is always best to add a separate testing class, this makes our code more transparent.

#5 We add the first test method.
Let's assume that we first want to test the so-called happy path, i.e. we test a method for numbers that should behave correctly. This method might look something like this:

public class CalculatorTests
{
 [Test]
 public void Add_WhenCalled_ShouldReturnSum()
 {
 //Arrange
 var calculator = new Calculator();

 //Act
 var result = calculator.Add(1, 2);

 //Assert
 Assert.AreEqual(3, result);
 }
}


 We name the method according to the naming convention:

MethodTestedName_Scenario_ExpectedBehavior


We test the Add method for two numbers and expect the correct result, i.e. the sum of these numbers. For a method to be a test, it must be marked with the [Test] attribute. Often developers also mark the class with the [TestFixture] attribute, this is because in older versions of NUnit it was required, but now as of version 2.5 it is not needed. Then, in the method body, we divide our code into three parts: AAA, i.e. Arrange, Act, Assert. In arrange there is a so-called preparation, i.e. objects are most often initialized here, in our example we create a calculator object. Act contains an action, i.e. we execute the method we want to test and call the Add method with the appropriate parameters. In assert we check whether the expected result is equal to the actual result. The Assert class is from the NUnit framework and contains many more static methods to verify the results of our tests. This division of methods is often used, thanks to which the test method becomes transparent. Of course, comments are not needed here, they can be deleted, I left them only for the clarity of this example.

#6 Install NUnit3TestAdapter.
If we do not have ReSharper, in order to run the tests, we must install one more component - NUnit3TestAdapter - via Manage NuGet Package.

#7 Running the test.
Tests can be run in various ways. If you are using Visual Studio, first, in the View tab, click Test Explorer, which will open a window with tests. Then you see the structure of your tests, you can run one test, or you can run all of them. Just right-click on the test name and click Run. Tests are run frequently, so it's best to use keyboard shortcuts for this in the future. You can use the default one or add your own. As you can see in test explorer our test light is red, this indicates an error in the Add method. After clicking on the testing method in test explorer, you can see the message: "Expected: 3 But was: -1". I intentionally made a mistake in our example, which we managed to detect in the test. We are improving the code of the Add method in the Calculator class.

public class Calculator
{
    public int Add(int number1, int number2)
    {
        return number1 + number2;
    }
}


The test is run again and now the test is green. This means the test passed.

Great, we wrote the first test. The above example wasn't too complicated, but I think it's worth starting with one to go through the whole process. In our example, we checked only one test case, but usually when we test, we need to check more paths.

We check all test conditions, if there are any conditional statements in the method, then the number of test cases increases. If we want to consolidate knowledge, we need more practice. To do this, we will write a unit test in C# for the famous FizzBuzz algorithm. Famous because recruiters often ask to implement it during job interviews :)

What is FizzBuzz?
It is a simple algorithm that returns the appropriate value based on the provided argument. The rules are:
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.

The implementation of the algorithm might look something like this:

namespace TestsDotNet
{
    public class FizzBuzz
    {
        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();
        }
    }
}


Unit test example in .NET


We will write our unit tests using the NUnit library. Following the steps detailed above:

  • We add a new project with tests, it will be TestsDotNet.UnitTests,
  • To the project with TestsDotNet.UnitTests tests, we add a reference to the project being tested, in this case TestsDotNet,
  • We install NUnit via NuGet,
  • We install NUnit3TestAdapter via NuGet.
  • We add a test class called FizzBuzzTests.

Now, for a moment, let's think calmly about what exactly we want to test, and then let's define the test cases that we want to test. Here we can use the requirements defined above. Starting from the first - if the argument is divisible by 3, returns "Fizz". Using the appropriate naming convention, this is:

MethodTestedName_Scenario_ExpectedBehavior


 We can name our method:

GetOutput_WhenInputIsDivisibleOnlyBy3_ShouldReturnFizz


 Don't forget to mark the method with the [Test] attribute.

[Test]
public void GetOutput_WhenInputIsDivisibleOnlyBy3_ShouldReturnFizz()
{
}


We write our test according to the AAA principle - Arrange, Act, Assert. First, in Arrange, we will prepare our object for testing. We want to test the FizzBuzz class method, so let's initialize this object first.

var fizzbuzz = new FizzBuzz();


In Act, the action takes place, i.e. we run the GetOutput method with the appropriate parameter - according to the scenario and return the value to a variable, for example named result.


var result = fizzbuzz.GetOutput(3);


 In Assert, we verify whether the result variable actually contains what we expect, i.e. "Fizz". Here we can use the Assert class of the NUnit framework, specifically the static method That. This means that we verify that the result, i.e. what the GetOutput(3) method returned, is equal to "Fizz".

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


And it's ready, we wrote a test for 1 scenario :) Our first testing method will look like this:

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

    var result = fizzbuzz.GetOutput(3);

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


Then we write a test for the next scenario, this time we expect that for parameter 5, the result "Buzz" will be returned. Our test will look very similar, we only change the argument to 5, the expected result in this case should be "Buzz":

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

    var result = fizzbuzz.GetOutput(5);

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


 We still need to verify whether "FizzBuzz" will be returned for an argument divisible by 3 and 5, but you already know how to do that.

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

    var result = fizzbuzz.GetOutput(15);

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


And also whether for an argument not divisible by 3 or 5, the same argument will be returned. There is no need to look for some magic numbers here, we can substitute 1 as an argument, i.e. a number that is not divisible by 3 or 5. If we substitute a number like 679, in the future when we return to this test, we will waste time and wonder I wonder what this number could mean in this case :) Is it a special number or just a random number that is not divisible by 3 or 5.

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

    var result = fizzbuzz.GetOutput(1);

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


We run our tests and a green color should appear in Test Explore, meaning that our tests have passed and our production code is working properly - we are deploying to production :)


FizzBuzzTests class:


using NUnit.Framework;

namespace TestsDotNet.UnitTests
{
    public class FizzBuzzTests
    {
        [Test]
        public void GetOutput_WhenInputIsDivisibleOnlyBy3_ShouldReturnFizz()
        {
            var fizzbuzz = new FizzBuzz();

            var result = fizzbuzz.GetOutput(3);

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

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

            var result = fizzbuzz.GetOutput(5);

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

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

            var result = fizzbuzz.GetOutput(15);

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

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

            var result = fizzbuzz.GetOutput(1);

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


Result in test explorer:

fizzbuzz_testexplorer.png


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


In today's article, I showed you what the entire process of testing a single method looks like. I introduced you to the theory, we wrote the first unit test in .NET. I showed you the entire process of writing a unit test. We have analyzed various test cases and this is most often what it looks like in our applications. We wrote out specific test cases, i.e. scenarios that we need to check, and then we wrote tests for them. All our tests passed successfully, so we know that our program works as intended. Having already written the tests, if in the future it turns out that something needs to be changed in our method, refactored the code, or added new functionality - we are sure that it will not cause any regression, because then it will be detected by unit tests. This example was simple because there were no external dependencies. I will describe how to deal with external dependencies in unit tests in the next article. See 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 - Automation Tests Explained in One Article
Next article - Whether to Unit Test Private Methods - Examples in C#
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

Search engine

© Copyright 2024 CodeWithKazik.com. All rights reserved. Privacy policy.
Design by Code With Kazik