Wednesday, July 31, 2019

Unit Testing C++ Code with Visual Studio

Unit Testing has been an inherent part of software development today. In Unit Testing, individual units of software are tested. While manual testing of individual functionality has been part of the development cycle, more recently these testing is automated with a testing framework.

In this article, I am going to illustrate the unit testing example in C++ using Visual Studio 2017 IDE and Native Unit Test Project. For illustration, unit test and actual software are contained within a solution. The code is contained in the basiccpp solution which currently includes one project and one test project and can be obtained from GitHub.

To get started, I have a solution named ‘basiccpp’ solution. The solution currently contains a console application, basiccpp, written in C++.

Step 1: Add a test project.
In the solution explorer, right-click to get the context menu and click ‘Add’ and select Test. Select the test framework. In this example, I am using ‘Native Unit Test Project’. The native unit test project resides in namespace “Microsoft::VisualStudio::CppUnitTestFramework”.


The test project created will test the functions within the basiccpp project. The basiccpp project is a window console application written in C++. 


Step 2: Add a reference

For the test project to get access to the basiccpp project, I will add a reference to the project.
Right-click the ‘References’ in the test project, and click Add Reference and in the window displayed, check the basiccpp project. If a single test project needs to test multiple projects, then I would select all the projects I am testing. 


Now the reference is added, I can start creating tests for the projects.

Step 3: Add Test
By default, Visual Studio template provides us with a unittest1.cpp file. I am going to rename this such that I know by looking at the file name the behavior I am testing. In this example, I am going to test Array.cpp file. Thus, I would name it as ‘ArrayTest.cpp’.

The specification of Array is in Array.hpp file and actual impelementaiton is in Array.cpp file. The content of this Array.hpp file is shown below:

#pragma once
#ifndef ARRAY_HPP_INCLUDED
#define ARRAY_HPP_INCLUDED
#endif

#include <cassert>
#include <ostream>
#include <utility>

template <typename T>
class Array {
private:
       T* m_ptr{ nullptr };
       int m_size{ 0 };
public:
       Array() = default;
       ~Array();
       //Constructor to create an array of defined size
       explicit Array(int size);
       //copy constructor
       Array(const Array& source);
       T& operator[](int index);
       T operator[](int index) const;
       void print() const;
       int Size() const;
       bool IsValidIndex(int index) const;
};

For testing, I will add test targeting publicly exposed the function of Array<T> class.
Since I am targeting functions defined in Array.hpp in basiccpp project, I am going to include Array.cpp in the ArrayTest.cpp by adding the following line:

#include "..\basiccpp\Array.cpp"

The Visual Studio will provide the IntelliSense as I am typing to the referenced project and I can select the correct include file.  




Step 4: Create tests

I am going to update the content of ‘ArrayTest.cpp’. In minimum, the structure of the test class one method with the following structure: 

namespace basicTest
{
       TEST_CLASS(my_test_class)
       {
       public:

              //test creation, size and [] operators.
              TEST_METHOD(my_test_method)
              {
                      //TODO: add test logic here.
              }
}
}

I can create as many methods within the class. The full content of the file test class for ArrayTest is shown below:

#include "stdafx.h"
#include "CppUnitTest.h"
#include "..\basiccpp\Array.cpp"
#include <functional>
#include <assert.h>

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace basicTest
{
       TEST_CLASS(ArrayTest)
       {
       public:

              //test creation, size and [] operators.
              TEST_METHOD(ArrayTest_Size)
              {
                     int size = 5;
                     Array<int> mArray{ size };
                     for (int i = 0; i < size; i++) {
                           mArray[i] = i;
                     }
                     Assert::AreEqual(size, mArray.Size());
                     Assert::IsTrue(mArray[0] == 0);
                     Assert::IsTrue(mArray[4] == 4);
              }

              //test the empty function
              TEST_METHOD(ArrayTest_Empty)
              {
                     int size = 5;
                     Array<int> mArray0{ 0 };
                     Assert::IsTrue(mArray0.IsEmpty());

                     Array<int> mArray{ size };
                     for (int i = 0; i < size; i++) {
                           mArray[i] = i;
                     }
                     Assert::IsFalse(mArray.IsEmpty());
              }

              //test  isValidIndex function
              TEST_METHOD(ArrayTest_IsValidIndex)
              {
                     int size = 5;
                     Array<int> mArray{ size };
                     Assert::AreEqual(size, mArray.Size());
                     Assert::IsTrue(mArray.IsValidIndex(1));
                     Assert::IsFalse(mArray.IsValidIndex(5));
              };

              //Test the method indexoutofbount by passing the invalid index and checking
              //if the exception is thrown
              TEST_METHOD(ArrayTest_IndexoutOfBound)
              {
                     int size = 5;
                     Array<int> mArray{ size };
                     auto func = [mArray, size] {
                           mArray[size + 100]; };
                     Assert::ExpectException<IndexOutOfBoundsException>(func);
              }

              //test copy constructor
              TEST_METHOD(ArrayTest_CopyConstructor)
              {
                     int size = 5;
                     Array<int> mArray{ size };
                     for (int i = 0; i < size; i++) {
                           mArray[i] = i;
                     }
                     Array<int> cArray = mArray;
                     cArray[0] = 100;
                     Assert::IsTrue(cArray[0] != mArray[0]);
              }
       };
}

Step 5: Run the test using Test Explorer
Once I have written the test, I can build and run the test using Test Explorer. In addition, by adding breakpoints to the method as well as in the test, I can debug the test. 


Additional Reference:
Write unit tests for C/C++ in Visual Studio
Using Assert::ExpectException with Native Unit Testing in VS11
GitHub page for the code
GitHub Link for Code