>  Blog

Unit Testing C# Code with MSTest


Pradyumn Sharma

June 27, 2017

About two months ago, I had written a blog titled "Test-Driven Development: An Introduction with a JUnit Example". In that post, I had explained the basic concepts of Test-Driven Development (TDD), taking the binary search algorithm as an example, and using Java, Eclipse, and JUnit as the platforms. In this post, I am going to discuss TDD using C#, Visual Studio 2015 (Community Edition), and MSTest.

We’ll dive right into the hands-on here, if you need an introduction to TDD, please refer to my earlier post.


Creating a Test Class

Begin by creating a new Unit Test Project. Steps:

File > New > Project > Test (under Templates, Visual C#), Unit Test Project



Name the solution "BinarySearchSolution", and the project "BinarySearchTestProject".

Visual Studio creates a class called UnitTest1, with the following default code:

namespace BinarySearchTestProject
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
        }
    }
}
   

Rename the class ‘UnitTest1’ to a more meaningful 'BinarySearchTest".

A method with the [TestMethod] attribute is an MSTest method. Conventionally, test methods are named using the scheme Test[thisbehavior].



Creating the Test Methods

I am going to have separate test methods for arrays with:

  • odd number of elements,
  • even number of elements, and
  • just one element.

So I replace the test method created by Visual Studio with the following methods, all empty initially:

 		[TestMethod]
        public void TestArrayWithOddNumberOfElements()
        {
        }
        [TestMethod]
        public void TestArrayWithEvenNumberOfElements()
        {
        }
        [TestMethod]
        public void TestArrayWithOnlyOneElement()
        {
        }		
 	

I implement only one test case in the TestArrayWithOddNumberOfElements() method, as follows:

 		[TestMethod]
        public void TestArrayWithOddNumberOfElements()
        {
            int[] collection = new int[] { 11, 22, 33, 44, 55, 66, 77 };
            Assert.AreEqual(0, BinarySearch.Search(collection, 11));
        }
    

Assert.AreEqual is one of the methods provided by MSTest, to compare the expected and the obtained results. Its syntax is:

Assert.AreEqual (<expectedValue>, <actualValue>)

where <actualValue> is replaced by a call to the method that we want to test.



Creating the BinarySearch Class

Visual Studio complains about the missing class BinarySearch. I need to have this class before proceeding further. I decide to keep the deliverable code and the test code in two separate projects under the same solution. So I create another project BinarySearchProject (Visual C#, Class Library) in the same solution.



I rename the automatically created class Class1.cs to BinarySearch.cs.

	namespace BinarySearchProject
	{
	    public class BinarySearch
	    {
	    }
	}

In the BinarySearchTestProject, I add a reference to the BinarySearchProject.



I go back to the BinarySearchTest class, and find that Visual Studio still complains about the missing BinarySearch class.



But that's easy to fix. I just have to specify "using BinarySearchProject":

using System;
using BinarySearchProject;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BinarySearchTestProject
{
    [TestClass]
    public class BinarySearchTest
    {
        [TestMethod]
        public void TestArrayWithOddNumberOfElements()
        {
       	int[] collection = new int[] { 11, 22, 33, 44, 55, 66, 77 };
            	Assert.AreEqual(0, BinarySearch.Search(collection, 11));
        }
        [TestMethod]
        public void TestArrayWithEvenNumberOfElements()
        {
        }
        [TestMethod]
        public void TestArrayWithOnlyOneElement()
        {
        }
    }
}

Visual Studio still complains about the missing "Search" method in the BinarySearch class. I ask Visual Studio to create one for me. I tweak the method generated by Visual studio, changing the name of the second parameter from "v" to "value":

	public class BinarySearch
    {
        public static int Search(int[] collection, int v)
        {
            throw new NotImplementedException();
        }
    }
    


Starting with a Failing Implementation

I must have some implementation in the Search method that returns an int. I'll worry about the actual implementation later, but for the time being, I write a failing implementation (an implementation that should always fail) as follows:

	public class BinarySearch
    {
        public static int Search(int[] collection, int v)
        {
            return -2;
        }
    }
    

With this failing implementation and a single test case, it is time to run the test. For this purpose, right-click the BinarySearchTest class and click "Run Tests".

Test fails, as I can see in the Test Explorer. That was expected, of course.



Visual Studio also lets me know which test method failed, and due to which assertion. It informs me that the expected value was 0, but the actual value that it got was -2.

No problem. I have taken the first baby steps for building my test infrastructure. Time for some serious work now.



Implementing the Test Methods

I now implement all the test methods in the BinarySearchTest class, as follows:

using System;
using BinarySearchProject;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BinarySearchTestProject
{
    [TestClass]
    public class BinarySearchTest
    {
        [TestMethod]
        public void TestArrayWithOddNumberOfElements()
        {
            int[] collection = new int[] { 11, 22, 33, 44, 55, 66, 77 };
            Assert.AreEqual(0, BinarySearch.Search(collection, 11));
            Assert.AreEqual(3, BinarySearch.Search(collection, 44));
            Assert.AreEqual(6, BinarySearch.Search(collection, 77));
            Assert.AreEqual(2, BinarySearch.Search(collection, 33));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 9));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 45));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 100));
        }
        [TestMethod]
        public void TestArrayWithEvenNumberOfElements()
        {
            int[] collection = new int[] { 11, 22, 33, 44, 55, 66, 77, 88 };
            Assert.AreEqual(0, BinarySearch.Search(collection, 11));
            Assert.AreEqual(3, BinarySearch.Search(collection, 44));
            Assert.AreEqual(4, BinarySearch.Search(collection, 55));
            Assert.AreEqual(6, BinarySearch.Search(collection, 77));
            Assert.AreEqual(7, BinarySearch.Search(collection, 88));
            Assert.AreEqual(2, BinarySearch.Search(collection, 33));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 9));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 45));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 100));
        }
        [TestMethod]
        public void TestArrayWithOnlyOneElement()
        {
            int[] collection = new int[] { 11 };
            Assert.AreEqual(0, BinarySearch.Search(collection, 11));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 9));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 45));
        }
    }
}

Without properly implementing the BinarySearch class, I run the tests again.



The tests fail again. All of them. As expected.



Implementing the BinarySearch Class

Time to implement the BinarySearch class, finally. With some logical thinking, and some trial and error, I end up with the following code that passes all the tests.

public class BinarySearch
    {
        public static int Search(int[] collection, int value)
        {
            int size = collection.Length;
            int lower = 0;
            int upper = size - 1;
            int middle;
            while (lower <= upper)
            {
                middle = (lower + upper) / 2;
                if (collection[middle] > value)
                {
                    upper = middle - 1;
                }
                else if (collection[middle] < value)
                {
                    lower = middle + 1;
                }
                else // we found the data
                    return middle;
            }
            return -1;
        }

This is how Visual Studio lets me know that all the tests have passed:




Trying Out Some Alternative Implementation

I decide to make a few changes to the BinarySearch class. I replace the statement

upper = middle - 1;

with

upper = middle;

And similarly, I replace the statement

lower = middle + 1;

with

lower = middle;

Here is the modified code:

public class BinarySearch
    {
        public static int Search(int[] collection, int value)
        {
            int size = collection.Length;
            int lower = 0;
            int upper = size - 1;
            int middle;
            while (lower <= upper)
            {
                middle = (lower + upper) / 2;
                if (collection[middle] > value)
                {
                    upper = middle;
                }
                else if (collection[middle] < value)
                {
                    lower = middle;
                }
                else // we found the data
                    return middle;
            }
            return -1;
        }
        

I run the code, hoping to see a green bar. Or fearing a red bar. But nothing appears. The program seems to have gone into an infinite loop. I manually cancel the tests (In the Test Explorer, there is a Cancel option).





Timeout as Test Failure

I can ask MSTest to report a test as having failed if it takes longer a specified amount of time. For example, given the amount of data in my test methods, five seconds can be a very generous upper limit of time for these to complete. I can specify this time limit with a Timeout parameter (in milliseconds) as shown in the code below:

	[TestClass]
    public class BinarySearchTest
    {
        [TestMethod, Timeout(5000)]
        public void TestArrayWithOddNumberOfElements()
        {
            int[] collection = new int[] { 11, 22, 33, 44, 55, 66, 77 };
            Assert.AreEqual(0, BinarySearch.Search(collection, 11));
            Assert.AreEqual(3, BinarySearch.Search(collection, 44));
            Assert.AreEqual(6, BinarySearch.Search(collection, 77));
            Assert.AreEqual(2, BinarySearch.Search(collection, 33));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 9));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 45));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 100));
        }
        [TestMethod, Timeout(5000)]
        public void TestArrayWithEvenNumberOfElements()
        {
            int[] collection = new int[] { 11, 22, 33, 44, 55, 66, 77, 88 };
            Assert.AreEqual(0, BinarySearch.Search(collection, 11));
            Assert.AreEqual(3, BinarySearch.Search(collection, 44));
            Assert.AreEqual(4, BinarySearch.Search(collection, 55));
            Assert.AreEqual(6, BinarySearch.Search(collection, 77));
            Assert.AreEqual(7, BinarySearch.Search(collection, 88));
            Assert.AreEqual(2, BinarySearch.Search(collection, 33));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 9));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 45));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 100));
        }
        [TestMethod, Timeout(5000)]
        public void TestArrayWithOnlyOneElement()
        {
            int[] collection = new int[] { 11 };
            Assert.AreEqual(0, BinarySearch.Search(collection, 11));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 9));
            Assert.AreEqual(-1, BinarySearch.Search(collection, 45));
        }
    }
    

I run my tests again. This time Visual Studio reports all the test methods as having failed because of timeout.





Back to Safety

As my experiment with the modification failed, I revert the code to the earlier version, and run the tests again. This time, as expected, all the tests pass again.



Final Words

This is an introductory tutorial on the basics of MSTest. MSTest has many more features to help in specifying the expectations from our code and verifying that these expectations continue to be met. For more details on MSTest, you may visit: https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest