Getting Started in CppUTest with C


If you are looking for a decent Unit Testing Framework that you can use with C, even in an Embedded Environment, then CppuTest is a good choice.

This article explains what CppUTest is and how to install and set it up. An example then shows how to use CppUTest with C.

CppUTest Start
Image partially taken from Gerd Altmann from Pixabay

What is the CppUTest Framework?

CppUTest is a Unit Testing Framework written in C++ and can be used in C++ but also pure C projects. It follows the nature of the xUnit Frameworks like JUnit, NUnit, etc. The creators of the framework state three core principles that they took into account during development:

  • Simple in design and simple in use.
  • Portable to old and new platforms.
  • Build with Test-driven Development for Test-driven Developers.
Homepage of CppUTestOpens in a new tab. (visited 2023/15/09)

Why Use a C++ Unit Test Framework for C?

You may ask why using a C++ Framework and not a C Framework (and there are some good Unit Testing Frameworks for C). In the end of the day it comes down to personal preference and features needed. CppUTest is very easy to use and straightforward in its syntax. This makes it a good choice for every C/C++ developer.

CppUTest itself uses Macros so no C++ knowledge is needed. It also uses only a primitive subset of C++ which makes it usable for embedded developers whose compilers do not always support all C++ features.

How To Install CppUTest (and set it up for C)

The first step is to visit the Homepage of CppUTestOpens in a new tab. and download the Framework. You will then most likely have a zipped folder which you have to unpack. Then you have to build the files with either CMake, make or Visual Studio. This depends on your preferences and the operating system you are using.

If everthing is fine you will have (and need) the folder with the header files (include) and a compiled library for your operating system and processor you are using, for example Windows x64, called CppUTest.lib.

For our purpose I will use the gcc (g++) compiler on Windows (mingw) and do the compiling and linking myself. To check if we are ready to use CppUTest we setup our project like this:

CalculatorExample/
|-- lib/
|   |-- CppUTest/
|       |-- bin/
|           |-- ****.dll
|       |-- include/
|           |-- ***.h
|-- src/
|   |-- main.c
|-- test/
|   |-- main.cpp

Example: The Calculator

To show you how to get started with CppUTest I will create some examples that create functions for a calculator program. These examples won’t be very complex or complicated as they should focus on the introduction of CppUTest and its functionality.

The CppUTest Test Runner

In our main.cpp we need to run the CppUTest Test Runner that will run all the tests that it finds in the compiled files. For this we need our main function with the command line arguments.

#include "../lib/CppUTest/include/CommandLineTestRunner.h"

int main(int argc, char** argv)
{
    return CommandLineTestRunner::RunAllTests(argc, argv);
}

Note that the Test Project itself is compiled in C++ whereas our application code is plain C.

We compile and link the test program, in Windows we do this with the following Command:

g++ main.cpp -o CalculatorTest -L"../lib/CppuTest/bin" -lCppUTest

We don’t have any tests and so CppUTest doesn’t find any. If we run the CalculatorTest program it will print this message:

Errors (ran nothing, 0 tests, 0 ran, 0 checks, 0 ignored, 0 filtered out, 0 ms)
Note: test run failed because no tests were run or ignored. Assuming something went wrong. This often happens because of linking errors or typos in test filter.

The First TestGroup and the AddTwoWholeNumbers Test

Tests in CppUTest are grouped together. This way you can intialize variables for a whole test fixture once instead of having them initialized in every test case.

For our calculator, we start with a function that returns the sum of two whole numbers. We create the Test Group with the TEST_GROUP macro. Because we don’t have any fixture initialization, the group decalaration is empty.

The first Test is declared with the TEST macro. Its first parameter is the name of the Test Group it belongs to, and the second parameter is the name of the test, in our case AddTwoWholeNummbers. The test itself is very simple, we call the add function with two numbers and check the result against our expected value.

In order to compare any two values against each other that are comparable with ==, you use CHECK_EQUAL(expected, actual). This asserts that the values are equal, if not, the test will fail

In order to use the CppUTest functionality we have to include TestHarness.h from the library. For our “real” application code we also have to include calculator.h which we will get to in a minute.

#include "../lib/CppUTest/include/CppUTest/TestHarness.h"
#include "../src/calculator.h"

TEST_GROUP(Add)
{    
};

TEST(Add, AddTwoWholeNumbers)
{
    int sum = add(4, 5);
    CHECK_EQUAL(9, sum);
}

The Interface for the add function is added to a file called calculator.h which is included in our test.cpp. It returns an int as result and takes two int parametes. They are both const because they won’t (and shouldn’t) be changed.

#pragma once

int add(const int a, const int b);

Now we implement the add function, but we only write enough code to make the program compile. We know that this will fail, but this is our intention. First write a failing Test and then change just enough to make the test pass.

#include "calculator.h"

int add(const int a, const int b)
{
    return 0;
}

The compiler command also needs to be adjusted. Please note that the paths are chosen relative to the test file because the command is executed in this directory.

g++ main.cpp add_test.cpp ../src/calculator.c  -o CalculatorTest -lCppUTest

You don’t have to include anything to main.cpp for the test runner to find the test. It automatically scans all compiled .cpp files for the TEST macros.

Because you always start with a failing test, and we expected a failing test, the result is exactly what we wanted. The test fails because the result is obviously wrong.

add_test.cpp:12: error: Failure in TEST(Add, AddTwoWholeNumbers)
        expected <9>
        but was  <0>
        difference starts at position 0 at: <          0         >
                                                       ^
.
Errors (1 failures, 1 tests, 1 ran, 1 checks, 0 ignored, 0 filtered out, 1 ms)

We write only enough code to make the test pass and run the test program again.

int add(const int a, const int b)
{
    return 9;
}
.
OK (1 tests, 1 ran, 1 checks, 0 ignored, 0 filtered out, 0 ms)

Test Triangulation

We know that returning a constant 9 is not the right solution for the add function so we add another test to prove it. This is called Triangulation like when you are trying to determine a point in space. In the real world this example would be too easy for Triangulation, but again, we want to learn about CppUTest and not about solving more or less complicated math problems.

TEST(Add, AddAnotherTwoWholeNumbers)
{
    int sum = add(3, 2);
    CHECK_EQUAL(5, sum);
}

Running the test fails as expected. Note: The two dots (..) indicate that two tests were run. If you look at the Terminal Output above you see that there is only one dot.

add_test.cpp:18: error: Failure in TEST(Add, AddAnotherTwoWholeNumbers)
        expected <5>
        but was  <9>
        difference starts at position 0 at: <          9         >
                                                       ^

..
Errors (1 failures, 2 tests, 2 ran, 2 checks, 0 ignored, 0 filtered out, 16 ms)

Making the test pass is easy enough, so I only show the code and the Terminal Output without further comments.

int add(const int a, const int b)
{
    return (a + b);
}
..
OK (2 tests, 2 ran, 2 checks, 0 ignored, 0 filtered out, 0 ms)

Checking Boolean

Let’s say you need to know if a number is positive. We would write a test for that and add a new function to our calculator interface.

Because we don’t have to compare our result to anything, we use the CHECK(…) macro from CppUTest which asserts that the given parameter is true. In our case it checks the result of our new function isNumberPositive(…).

TEST(Add, IsNumberPositive)
{
    CHECK(isNumberPositive(5));
}

The interface is again simple, but in C we do not have a “natural” boolean type. To use bool we include the stdbool.h file and then declare our function.

#pragma once
#include <stdbool.h>

int add(const int a, const int b);
bool isNumberPositive(const int number);

For our failing test we add the definition of isNumberPositive that returns false.

bool isNumberPositive(const int number)
{
    return false;
}

If you now look at the test failure message you see that it is different from our other failure messages. Also note the third dot for our third test.

add_test.cpp:23: error: Failure in TEST(Add, IsNumberPositive)
        CHECK(isNumberPositive(5)) failed

...
Errors (1 failures, 3 tests, 3 ran, 3 checks, 0 ignored, 0 filtered out, 2 ms)

To make it pass we change false to true although we already know the solution without Triangulation. But I will nevertheless write another test for this, because I want to show you another macro.

bool isNumberPositive(const int number)
{
    return true;
}
...
OK (3 tests, 3 ran, 3 checks, 0 ignored, 0 filtered out, 0 ms)

Sometimes it is more convienient to check if something is false instead of true. For this case you can use the CHECK_FALSE macro. We add another test to demonstrate how this would look like. We simply invert our expectation by expecting to get false as return value from isNumberPositive.

TEST(Add, IsNumberNegative)
{
    CHECK_FALSE(isNumberPositive(-4));
}
add_test.cpp:28: error: Failure in TEST(Add, IsNumberNegative)
        CHECK_FALSE(isNumberPositive(-4)) failed

....
Errors (1 failures, 4 tests, 4 ran, 4 checks, 0 ignored, 0 filtered out, 3 ms)

Now we write the implementation to make our test pass. For the sake of simplicity we assume that zero is a negative number.

bool isNumberPositive(const int number)
{
    return (number > 0);
}
....
OK (4 tests, 4 ran, 4 checks, 0 ignored, 0 filtered out, 0 ms)

Checking Floating Point Numbers

As you may know you cannot compare floating point numbers with == and therefore checking equality is not that easy. For this case, CppUTest has the macro DOUBLES_EQUAL(…) where you add tolerance as a third parameter.

Because we cannot overload functions in C we have to declare a new function with another name for our adding floating point numbers function. But first we write the test that will check the result of two floats. As tolerance we choose 0.01 which is a reasonable value for our purposes.

TEST(Add, AddTwoFloatNumbers)
{
    float sum = add_d(1.22, 2.31);
    DOUBLES_EQUAL(3.53, sum, 0.01);
}

The interface is expanded to include the new function. Its definition simply returns 0 to make the code compile and our new test fail.

float add_d(const float a, const float b);
float add_d(const float a, const float b)
{
    return 0;
}
add_test.cpp:34: error: Failure in TEST(Add, AddTwoFloatNumbers)
        expected <3.53>
        but was  <0> threshold used was <0.01>

.....
Errors (1 failures, 5 tests, 5 ran, 5 checks, 0 ignored, 0 filtered out, 2 ms)

We now the solution from our first test and so we really save ourselves the triangulation this time.

float add_d(const float a, const float b)
{
    return (a + b);
}
.....
OK (5 tests, 5 ran, 5 checks, 0 ignored, 0 filtered out, 0 ms)

Now, out of curiosity, if we replace the DOUBLES_EQUAL(…) function with the simple CHECK_EQUAL(…) function, the test fails. Please note the somewhat strange error message that <3.53> was expected, but <3.53> was received. Although the numbers look the same, they are not. Therefore you need DOUBLES_EQUAL(…) with a tolerance.

add_test.cpp:34: error: Failure in TEST(Add, AddTwoFloatNumbers)
        expected <3.53>
        but was  <3.53>
        difference starts at position 32 at: <>

Checking Strings (Character Arrays)

Another special case is the comparison between Strings. If you want to compare two (const) char* values in C you will use strcmp(…) and this is exactly what the STRCMP_EQUAL(…) macro in CppUTest does, plus it asserts and fails if the strings are not equal.

To show the functionality of this macro we add a final test and another function that returns the result of the add function as a char*.

TEST(Add, AddTwoWholeNumbersAndGetResultAsString)
{
    char* sumAsText = add_s(1, 2);
    STRCMP_EQUAL("3", sumAsText);
}

The declaration and defintion are straightforward.

char* add_s(const int a, const int b);
char* add_s(const int a, const int b)
{
    return "";
}

We fail and this is a good thing.

dd_test.cpp:40: error: Failure in TEST(Add, AddTwoWholeNumbersAndGetResultAsString)
        expected <3>
        but was  <>
        difference starts at position 0 at: <                    >                                                       ^

......
Errors (1 failures, 6 tests, 6 ran, 6 checks, 0 ignored, 0 filtered out, 2 ms)

For the solution of this we have to do two things. First we implement the conversion of int into char* and return this from our add_s(…) function. For this we have to inlude stdio and stdlib.

#include "calculator.h"
#include <stdio.h>
#include <stdlib.h>

...

char* add_s(const int a, const int b)
{
    int sum = a+b;
    char* buffer = (char*)malloc(sizeof(int));
    sprintf(buffer, "%d", sum);
    return buffer;
}

Next we have to modify our test a little bit, because we used malloc in the function we now have to free it after the assertion so that we do not create a memory leak. Then everything is fine and all tests pass.

TEST(Add, AddTwoWholeNumbersAndGetResultAsString)
{
    char* sumAsText = add_s(1, 2);
    STRCMP_EQUAL("3", sumAsText);
    free(sumAsText);
}
......
OK (6 tests, 6 ran, 6 checks, 0 ignored, 0 filtered out, 1 ms)

Similar to the DOUBLES_EQUAL(…) function above, if we replace the STRCMP_EQUALS(…) with our “normal” CHECK_EQUAL(…) function, the test would fail with a somewhat confusing message.

add_test.cpp:40: error: Failure in TEST(Add, AddTwoWholeNumbersAndGetResultAsString)
        expected <3>
        but was  <3>
        difference starts at position 32 at: <>
                                              

......
Errors (1 failures, 6 tests, 6 ran, 6 checks, 0 ignored, 0 filtered out, 2 ms)

The Bowling Kata in C with CppUTest

“Kata” means “Form” and is a strictly choreographed set of movements, made to practice alone. A Programming Kata therefore is a choreographed exercise to practise programming skills.

A definition of the Bowling Kataand some more exercises is described here at TDDManifestoOpens in a new tab.. The short version is that we write the scoring algorithm of a bowling game, including spares and strikes.

You can see the full implemetation of the Bowling Kata in C with CppUTest in this Article.

Other C++ Frameworks for C

There are other C++ Frameworks that you can use for your C Development. The most well known Unit Test Framework that falls into this category is Google Test (or GTest).

If you are interested in Google Test you can find an article about the setup and writing a Hello World Project here.

This article was first published by Marco Lieblang on moderncprogramming.com. If you are reading this somewhere else, it may be plagiarism.

Marco Lieblang

Professional Programmer since 2003, passionate Programmer since the mid 90's. Developing in many languages from C/C++ to Java, C#, Python and some more. And I also may know a bit about Assembly Languages and Retro Systems.

Recent Posts