Unit testing in C# – basics

0

This is a unit.

public void BubbleSort()
{
    for (var i = 0; i < items.Count; i++)
    {
        for (var j = 0; j < items.Count - 1; j++)
        {
            if (items[j] > items[j + 1])
            {
                var tmp = items[j];
                items[j] = items[j + 1];
                items[j + 1] = tmp;
            }
        }
    }
}

This is a test.

public void BubbleSort_BasicTest()
{
    var sortedCollection = new SortedCollection();
    var testList = new List<int> { 6, 3, 7, 1, 2, 8 };

    sortedCollection.BubbleSort(testList);

    var expectedList = new List<int> { 1, 2, 3, 6, 7, 8 };
    CollectionAssert.AreEqual(expectedList, testList);
}

This test tests the functionality of the unit of code. That is why this test can also be called a unit test. Unit testing is the practice of writing extra code in addition to your normal (production) code, in order to verify the functionality of that code. This post will help you start out with unit testing in C#, it will introduce the absolute basics necessary for you to start writing your own unit tests and after going through it you will be able to write your own unit tests for e.g. your school assignment.

Unit testing frameworks

There are basically three main unit testing frameworks available in C#. There’s MSTest (v2), NUnit (3) and xUnit. You might think that choosing the right framework is overwhelming. However, the frameworks are very similar, so the choice doesn’t matter so much (generally). You’ve got a bunch of attributes to decorate your methods with and you simply write some code to do some testing.

In the past choosing the right framework was a bit harder, because there were larger differences in the features which each framework supported. Nowadays they have all gotten to pretty much the same level and as I said, they are very similar. In this post, I will show you the basics of MSTestv2 and also NUnit3, so you can see for yourself what the differences between these frameworks are. I haven’t really worked with xUnit, that is why I am not going to concern myself with it.

First we need some code to test

I have prepared a very simple class called SortedCollection. SortedCollection has only a single property (or field, whatever you want to call it) and that is a List of integers called items. It has three constructors. One is the default constructor, which I could have removed but haven’t. The other two are constructors which initialize the items list. One creates a copy of a provided list and assigns it to the items property, the other transforms an array into a List (basically also copying the array items) and also assigns it to the items property. Both of these constructors also BubbleSort the items by calling the BubbleSort() method.

The BubbleSort() method simply sorts the items list using the simple sorting algorithm Bubble Sort. I have also overriden the ToString() method so that it would be easier to debug the code and to view the collection if I wished to do so. I am not going to paste the code here, you can take a look at it in this github repo.

Unit Test Project

Before I tried unit testing, the usual way of testing my code (school assignments mostly) was to write some code, which would test the current functionality, in to the main function of the program . The problem with this method is, that once you are done testing your code, you will either delete that code or comment it out and move on. You write new functionality and after a while your main function is bloated with commented code or the code is deleted. Either way, none of the code is actually being tested anymore. As code changes (which it does) you need a way to test it easily and consistently.

A unit test project is a special type of a project. It is a separate project from the production project. You can create it in Visual Studio by either creating a new project using the Unit Test project template or by right clicking a class or a method in code and choosing Create Unit Tests. This project then behaves as an independent project and the tests in it are run separately from the production project.

Solution explorer with unit tests
Fig. 1 – solution explorer with unit tests

You can see from Fig. 1, that the unit test projects are separate projects. They contain their own files, their own dependencies (e.g. the project under test), their own build configurations, dlls and so on.

Creating The Project From Scratch

Note: Feel free to skip this section if you aren’t interested in doing stuff manually.

One way to create a unit test project is to create it by yourself. This can be done via right clicking the solution in the Solution Explorer and then choosing Add -> New Project. Here you can use the search bar on the right to look for „Unit Test„. You should look for the Unit Test Project (.Net Framework) project template.

Unit test project creation
Fig. 2 – Unit test project creation

The standard way of naming your project is [ProductionProjectName].UnitTests or simply something, which enables you to differentiate this project from the production project and also states that it is a unit test project.

After clicking the OK button you should have a new project in your solution explorer and there should also be one file.

Solution after unit test project creation
Fig. 3 – Solution after unit test project creation

Now since this is pretty much an empty project, we need to include all our dependencies. Let’s start by adding a reference to the MySortedCollection project. Right click References under the unit test project and select Add Reference…. In the dialog select Projects and check the check box next to MySortedCollection. Click OK and that is it. Now our unit test project is able to see our project under test.

Adding a Unit Testing Framework

Next we have to add a unit testing framework. Visual Studio is a bit sneaky and the project template already contains the MSTestV2 framework. So you can either use that or if you want to use another framework you should go through the following steps. Right click on the unit test project and choose Manage NuGet Packages…. Go to the Installed tab and delete both of the MSTest packages. Then navigate to the Browse tab and search for „NUnit“. You should install the package named simply „NUnit“. However you also need the package called NUnit3TestAdapter which enables Visual Studio to run your tests.

That is it. Your project is now ready. Don’t worry if this looks a bit too complicated, there is also an easier way of doing this.

Letting Visual Studio Create The Project

The easier way of creating a unit testing project is to right-click a function or a class in your production code and then selecting Create Unit Tests. You will get a dialog, which you can see in Fig. 4.

Create Unit Tests dialog
Fig. 4 – Create Unit Tests dialog

The dialog is pretty straightforward. The important part is the framework you want the project to contain. After clicking OK, Visual Studio will create a test project. The project will also contain a file with the name you have set in the dialog and there will be a single test method in the class. The best part about this is, that everything has already been setup for you. Both the production project and the framework of your choice have been added to the project as references. The only thing left is to write our first unit test.

Our First Unit Test

Let’s go ahead and create our first unit test. Below you can see both the MSTestV2 version and the NUnit3 version of the same test. As you can see, they aren’t really that different. The only difference in this case is that MSTestV2 uses the TestClass and TestMethod attributes to mark test classes/functions. Whereas NUnit3 uses the TestFixture and Test attributes. That’s it. Nothing else to it (for now).

[TestClass]
public class SortedCollectionTests
{
    [TestMethod]
    public void ConstructorWithList_SortsList()
    {
        //ARRANGE
        var numbers = new List<int> { 6, 3, 7, 1, 2, 8 };

        //ACT
        var sortedCollection = new SortedCollection(numbers);
        var collectionString = sortedCollection.ToString();

        //ASSERT
        var expectedCollectionString = "1,2,3,6,7,8";
        Assert.AreEqual(expectedCollectionString, collectionString);
    }
}
[TestFixture]
public class SortedCollectionTests
{
    [Test]
    public void ConstructorWithList_SortsList()
    {
        //ARRANGE
        var numbers = new List<int> { 6, 3, 7, 1, 2, 8 };

        //ACT
        var sortedCollection = new SortedCollection(numbers);
        var collectionString = sortedCollection.ToString();

        //ASSERT
        var expectedCollectionString = "1,2,3,6,7,8";
        Assert.AreEqual(expectedCollectionString, collectionString);
    }
}

So what does this test actually do? Well, let’s start with the basics. Each unit test should have three phases (or mostly they do). I have marked these phases in the code, this is just for the purposes of this post, it isn’t normal practice to include the comments. The first phase is Arrange. You setup everything you need for your unit test. You create your classes under test and some other classes you need for the test to actually run. Then comes the Act phase. This is the phase, when the thing you are testing happens. You call a method, create an object (if you are testing a constructor), assign a value to a property and so on. The third phase is the Assert phase. In this phase you compare expected results to the actual results you have received.

In the context of our example test the Arrange phase is the creation of a list of numbers. This list will be used to create our sorted collection in the Act phase. The act is the instantiation of our SortedCollection, since that is what we are testing. We are testing that the constructor, which takes a list of numbers, also sorts that list. Note: In the example I have also included the ToString() call in the act phase, which is probably wrong, but I realized this after creating the videos so I didn’t want to change it anymore. In the Assert phase we set the expected string and we Assert that the expected string is equal to the actual collection string. This basically compares the two strings. If they aren’t equal, the Assert.AreEqual() method throws an exception with a nice message explaining what has happened.

The Assert Class

Assert is a static class which contains some methods to help you make checks in your test code. The methods check your condition and if the condition fails, it’ll also make the test fail with a nice message to it. These methods are pretty straightforward so I will just mention a couple of them, without explaining them.

  • Assert.AreEqual(object, object)
  • Assert.AreEqual(double, double, epsilon) – this overload of the method is worth mentioning. It exists because of floating point arithmetic and its (im)precision. Epsilon is the maximum difference, which can be between the two numbers for them to be considered equal.
  • Assert.IsTrue(bool), Assert.IsFalse(bool)
  • Assert.IsNull(object), Assert.IsNotNull(object)
  • Assert.ThrowsException(Action) – useful if you expect an excetion to be thrown

The Assert class is framework specific, but the classes don’t differ too much. There is also a special class for asserting collections – CollectionAssert. It contains some methods to compare two collections – compare with ignoring the order, with the exact same order, …. CollectionAssert is however beyond the scope of this post.

Running Unit Tests

Now that we have written our first unit test we should be able to run it somehow. Luckily running unit tests in visual studio (or in most other IDEs) is very straightforward. You can either right-click the unit test and select Run Test(s). This should start building the project and it should also open the Test Explorer window in Visual Studio. If the Test Explorer doesn’t open automatically, you can open by going to Test -> Windows -> Test Explorer or by pressing Ctrl + Alt + U.

Test Explorer
Test Explorer

After it has run you can see the results in the Test Explorer. The Test Explorer conveniently sorts your tests by namespace so it easy to navigate to them. You can see whether the test has failed or passed and if it has failed, you can see the output of the test by selecting it in the explorer. Also at the top of the Test Explorer you have some buttons which enable you to run different groups of tests (failed tests, not run tests, repeat last run, …).  The tests can also be run by right-clicking them in the Test Explorer. Doing so runs all of the tests which belong to that tree.

Parametrized Unit Tests

So far we have learned how to write a simple unit test and how we can actually run it. Now we are going to learn a way of making our unit test a bit cleaner with less repeated code. Both of the frameworks covered in this post have the option of having parametrized tests. This means that you add parameters to your tests and then fill them accordingly.

For example, let’s say we have an addition function. This function takes two numbers as parameters and produces the sum as a result. If you were to write simple tests for this method, you could end up having three test methods. The first one would test that 2+2=4, the scond one that -1+3=2 and the third one would test that double.NaN+2=double.NaN. Instead of all that code, you can write just one method, but with three parameters and three parameter sets (2, 2, 4), (-1, 2, 1), (double.NaN, 2, double.NaN).

// three methods
[TestMethod]
public void Addition_2Plus2Is4()
{
  ...
  var result = calculator.Add(2, 2);
  var expected = 4;
  ...
}

[TestMethod]
public void Addition_Minus1Plus3Is2()
{
  ...
  var result = calculator.Add(-1, 3);
  var expected = 2;
  ...
}

[TestMethod]
public void Addition_NaNPlus2IsNaN()
{
  ...
  var result = calculator.Add(double.NaN, 2);
  var expected = double.NaN;
  ...
}

//vs one method with parameters
[DataTestMethod]
[DataRow(2, 2, 4)]
[DataRow(-1, 3, 2)]
[DataRow(double.NaN, 2, double.NaN)]
public void Addition_MultipleTestCases(double firstNumber, double secondNumber, double expected)
{
	...
	var result = calculator.Add(firstNumber, secondNumber);
	...
}

The great thing about the parametrized test is, that the test cases are treated as independent tests. You can see them as separate tests in the Test Explorer. If one of them fails, the other tests run anyways and you can see the failure messages for each failed test case.

In the context of our application I decided to test the Add() function this way. You can see the test in the code snippet below. The first parameter is the number I will be adding to my collection. The second parameter is the string I expect to get when I call ToString() on my collection after adding the number. Note: I have to say again, that the choice to use ToString() isn’t a very fortunate one.

[DataTestMethod]
[DataRow(0, "0,1,2,3,6,7,8")]
[DataRow(9, "1,2,3,6,7,8,9")]
[DataRow(4, "1,2,3,4,6,7,8")]
[DataRow(5, "1,2,3,5,6,7,8")]
public void Add_VariousNumber(int numberToAdd, string expectedCollectionString)
{
    // Arrange
    var numbers = new List<int> { 6, 3, 7, 1, 2, 8 };
    var sortedCollection = new SortedCollection(numbers);

    //ACT
    sortedCollection.Add(numberToAdd);
    var collectionString = sortedCollection.ToString();

    //ASSERT
    Assert.AreEqual(expectedCollectionString, collectionString);
}

Initializing Unit Tests

One more topic I am going to talk about in this tutorial is the initialization of your unit tests. While writing unit tests you might notice that in a class most of the tests start in the same way. You decide to refactor your code so you create an Initialize() method which contains the repeating lines and you call that function at the beginning of each test. However, there is a better way of refactoring this code.

Initialize Each Test

Both MSTest and NUnit provide you with an attribute which makes a method run before each test. In MSTest this attribute is TestInitialize and the method takes no parameters. In NUnit the attribute is SetUp and the method also doesn’t take any parameters. These methods are run before each test, which allows you to put various initialization code into them.

In the test code below, you can see our MSTestV2 version of test initialization. I have decided to factor out the creation of our collection into this method, because it was repeated in all of our tests. Be careful while applying refactoring like this, because it might make your code unreadable.

[TestInitialize]
public void TestInitialize()
{
    numbers = new List<int> { 6, 3, 7, 1, 2, 8 };
    sortedCollection = new SortedCollection(numbers);
}

Initialize Only Once

There are also attributes available for methods, which should be run only once for each test class. The attribute in MSTest is ClassInitialize and MSTest also requires this method to have a given method signature, which you can see in the code snippet. In NUnit this feature is available via the OneTimeSetUp attribute and in this case, you don’t need any special signatures.

If you are wondering what you would use this for, think of singletons for example. All of your tests might need some kind of a singleton to be initialized in order to run properly. Creating the singleton is expensive though and takes about 200ms. If you have 100 tests in this class and you initialize the singleton before each test, your tests are going to run for 20 seconds (at least). By initializing the singleton only once, your 100 tests will run more than 19 seconds quicker.

Test Deinitialization

There also attributes for deinitializing your unit tests if you wish to do so. This might be useful for object disposal, resetting some variables etc. MSTestV2 has the TestCleanup and ClassCleanup attributes. NUnit3 uses TearDown and OneTimeTearDown. I am sure you can figure out which method is run when. Also you can notice once again, that the frameworks are very similar.

Good Luck

That is all you shall need to start writing your own unit tests. If you feel like you need to know more about unit test here are some topics you might want to search for:

  • mocking
  • TDD – test driven development
  • Integration Testing
  • difference between unit tests and integration tests

Also I recommend you read this books on unit testing:

There will also be more on unit testing on StreetOfCode in the foreseeable future.

Good Luck and Have Fun unit testing 😉


Pridaj komentár

Vaša e-mailová adresa nebude zverejnená.