Jun 6, 2023 by Thibault Debatty | 5568 views
Writing unit tests is considered a good development habit for numerous reasons. Indeed, unit tests guarantee that the code works as expected, and they prevent developers from accidentally breaking things. Finally, they allow to see how the program is improving with each new commit, and they can be used as documentation to show how the program should be used by others. In this blog post, we show a simple way to implement unit tests when you are writing C code.
The main idea is to:
Finally, gcov will be used to compute the code coverage of the test.
To illustrate this, we will use a simple implementation of a counter. All the source code can be found on the following GitLab repository: https://gitlab.cylab.be/cylab/examples/c-unit-testing
So, for this example we will test the implementation of a simple counter. The main point is that the code of the counter is kept separated from the main app source code. So we will have a counter.h with something like:
/*
* A very simple counter.
* https://cylab.be/blog/271/unit-testing-c-code
*/
struct Counter;
/**
* Instantiate a new counter.
*/
struct Counter *counter(void);
/**
* Increment the value of the counter.
*/
void increment(struct Counter *);
/**
* Get the current value of the counter.
*/
int value(struct Counter *);
And a counter.c with something similar to:
/**
* A simple counter.
* https://cylab.be/blog/271/unit-testing-c-code
*/
#include <stdlib.h>
#include "counter.h"
struct Counter {
int value;
};
struct Counter * counter(void)
{
struct Counter* c = malloc(sizeof(struct Counter));
c->value = 0;
return c;
}
void increment(struct Counter* c)
{
c->value++;
}
int value(struct Counter* c)
{
return c->value;
}
And the main method should go in a dedicated file, like app.c:
/**
* Example app, that uses a counter.
* https://cylab.be/blog/271/unit-testing-c-code
*/
#include "counter.h"
int main(int argc, char **argv)
{
// do someting with the counter...
}
Now, to test our counter, we will create a dedicated test file test.c:
#include "counter.h"
#include <assert.h>
int main(int argc, char **argv)
{
struct Counter* c = counter();
assert(c);
assert(value(c) == 0);
increment(c);
assert(value(c) == 1);
return 0;
}
As you can see, this file:
"counter.h"
and <assert.h>
;assert()
to check the working of the counter.So to run the test, we have to compile and run test.c, which we will do by adding some lines to the Makefile:
CC=gcc
CFLAGS=-std=c99 -Wall
# additional flags for gcov
TESTFLAGS=-fprofile-arcs -ftest-coverage
test: test.c counter.h counter.c
# build the test
$(CC) $(CFLAGS) $(TESTFLAGS) -o test test.c counter.c
# run the test, which will generate test-counter.gcna and test-counter.gcno
./test
# compute how test is covering counter.c
gcov -c -p test-counter
clean:
rm -f *.o test *.gcov *.gcda *.gcno
As you can see, to compile the test, we use additional gcc flags profile-arcs
and test-coverage
. With these flags, when test
is executed, it will produce 2 additional files (.gcna
and .gcno
) that can be used by gcov to compute the code coverage of the test.
We can now run the test with make test
:
Gcov produces a short summary on screen, and also a report called counter.c.gcov in our example, that show how many times each line has been executed…
Once you have written your tests, you can add them to your .gitlab-ci.yaml:
test:
image: gcc:11
script:
- make test
This blog post is licensed under CC BY-SA 4.0