Minimalistic unit tests in C. Uses the __attribute__((constructor))
which, as far as I know, is supported by GCC
and clang
. So this probably only works for those compilers.
To see the unit tests run:
$ git clone [email protected]:rubenvannieuwpoort/c_unit_tests.git
$ cd c_unit_tests
$ make test
Make a separate C source file for your tests that includes test.h
. This file should not contain a main
-- it is provided by the test.h
header file. A unit test now looks like this:
UNIT_TEST(test_name) {
// do something useful here
test_assert(...);
}
This file can be compiled and run as usual. When running, it displays something like
test_name... SUCCESS
1 tests passed, 0 tests failed
And the program will exit with code 0 if all tests passed and 1 if there are failing tests.
I like to make a target test
in the Makefile
that runs the test binary, so that you can do make test
to run the tests. See this repo for example:
$ make test
gcc testsuite.c -ggdb -O3 -o testsuite
./testsuite
hello... SUCCESS
goodbye... SUCCESS
loopy... SUCCESS
3 tests passed, 0 tests failed
Short story, it uses some macro magic.
Long story:
UNIT_TEST
is a macro. If you write UNIT_TEST(my_test)
, this line expands to
static void my_test(void);
static void __attribute__((constructor)) __construct_my_test(void) {
add_test(my_test);
}
static void __real_my_test(void);
static void my_test(void) {
printf("%s... ", __func__);
fflush(stdout);
__real_my_test();
}
static void __real_my_test(void)
Note that there is intentionally no semicolon on the last line: UNIT_TEST(my_test)
should be followed by a block that makes up the body of the test.
This code:
- Declares
my_test
but doesn't define it (a so-called forward declaration). This is necessary so that in the next step, we can usemy_test
without having defined it. - Defines a
__construct_my_test
function. This has the__attribute__((constructor))
attribute, which means the function is executed even beforemain
is executed (this is GCC-specific, see this page for more information). This code then runsadd_test(my_test)
, which adds a function pointer tomy_test
to a global linked list (defined intest.h
). - We have another forward declaration, now for
__real_my_test
, the function that will contain the actual unit test code. - It then defines a function
my_test
which printsmy_test...
and then runs__real_my_test
. __real_my_test
now contains the code block that was placed afterUNIT_TEST(my_test)
as its body.
The main
method provided by test.h
traverses the linked list with function pointers that was initialized by the __construct_my_test
method (and other such methods if you have more testcases), and runs the functions in the linked list.
Then, the __real_my_test
function is the actual tests which calls test_assert(condition)
, which, again, is a macro. It expands to:
do {
if (condition) {
printf("SUCCESS\n");
tests_passed++;
}
else {
printf("FAILED\n");
tests_failed++;
}
} while(0)
This do ... while
is there to make sure the behavior is right when the macro is used in any context (see this page for a more in-depth explanation). The other code is straightforward. It prints SUCCESS
or FAILED
(which comes after the mytest...
which was printed in my_test
, assuming that no other stuff is printed before test_assert
is called) and keeps track of the number of passed and failed tests.
Before terminating, main
prints the number of tests passed and failed (or No tests are found
if no tests have run at all).