Creating unit tests in C++

As your code grows larger, it is likely to become unmaintainable. Using unit tests may help you get back in control over your code. This guide gives a short introduction to unit testing.

When you want to make sure that your code is working properly, it is a good idea to divide it into smaller, independent pieces that may be tested individually. A unit test is a short  code that tests a smallest possible portion of your application. It is a good idea to write tests as you go and it can even be useful to write a test before you even implement the function that will be tested.

Download UnitTest++

UnitTest++ is an extremely lightweight testing library that is very easy to install in Ubuntu. Just type the following in a terminal:

sudo apt-get install libunittest++-dev

For other platforms, see the UnitTest++ homepage for more information.

Setting up your first test

In the long run, you should have your unit test project side-by-side with your original project. In other words, in a folder next to your source folder. We have written an article about such a setup with Qt Creator that you find here.

For now, we'll just assume that we want to test a class that we'll declare directly in the tests project. This class has a function "addition" that currently doesn't work. It multiplies instead of adding the two numbers it receives:

myclass.h:

class MyClass {
public:
    double addition(double a, double b) {
        return a * b;
    }
};

To test this, we create a main.cpp file that includes the class together with the UnitTest++ library:

main.cpp:

#include <unittest++/UnitTest++.h>
#include <myclass.h>

TEST(MyMath) {
    MyClass my;
    CHECK(my.addition(3,4) == 7);
}

int main()
{
    return UnitTest::RunAllTests();
}

Compiling and running this file will result in the following terminal output:

../../MyProject/tests/main.cpp:72: warning: Failure in MyMath: my.addition(3,4) == 7
FAILURE: 1 out of 1 tests failed (1 failures).
Test time: 0.00 seconds.

As you can see, this output tells you exactly what test failed and what it tried to do. You may now fix the MyClass::addition function to see that the error goes away:

myclass.h:

class MyClass {
public:
    double addition(double a, double b) {
        return a + b;
    }
};

The output now shows that the error is gone, so the test passes:

Success: 1 tests passed.
Test time: 0.00 seconds.

 

Motivation for unit tests

In short, the goals of unit testing are:

  1. Verify your functions and classes. Check that they do what you expect them to.
  2. Make sure you don't introduce errors in your code at a later point in time.
  3. Split your tests into smaller pieces to make it easier to pinpoint the true error.

So, why should you do this? First of all because you should check that your functions do what you believe they do. When working with the wave functions in FYS4411 you are likely to implement some parts of it wrongly. Noticing the error is often done by seeing that your final output, the energy, is off. Then you might start writing to the terminal all over your code to see if the functions return sane values. But it is not easy to see that the functions return good values when your input is mostly random positions and parameters.

Then it is much better to do a couple of examples on paper, calculate them by hand and implement tests that check your implementation against the results you get by hand. Such a possible test could be to check that the numerical gradient for your wave functions is correct. If we have a DummyWaveFunction defined as the sum of all coordinates squared:

class DummyWaveFunction : public WaveFunction {
public:
    DummyWaveFunction(int nParticles, int nDimensions, int nParameters) : WaveFunction(nParticles, nDimensions, nParameters) {
    }
    double evaluate(const mat &positions) {
        double evaluation = 0;
        for(int i = 0; i < m_nParticles; i++) {
            evaluation += dot(positions.row(i), positions.row(i));
        }
        return evaluation;
    }
};

You can test the numerical gradient of this against some given data like this:

TEST(testNumericalGradient) {
    DummyWaveFunction wf(2,3,3);
    mat positions = { 1,2,3,4,5,6 };
    positions.reshape(2,3);
    wf.setNextPositions(positions);
    wf.initialize();

    mat correctGradient = {2,4,6,8,10,12};
    correctGradient.reshape(2,3);

    mat difference = abs(wf.nextGradient() - correctGradient);

    CHECK(difference.min() < 1e-4);
}

In addition to verifying that your results are correct when you implement them, unit tests also help you make sure you don't introduce errors later on. If you maintain your tests well, you will be able to check that your functions still return what they are supposed to. This really helps finding those errors that sneak up on you when you edit a part of the code that suddenly introduces an error somewhere you wouldn't expect it to.

Integrate unit testing in Qt Creator

This article explains how you can integrate UnitTest++ with Qt Creator.

Published Jan. 16, 2014 1:23 PM