Introduction
If your project is like others, you’ll probably have a util.h and util.c files containing general utility functions. For example, suppose you want a function strnspn that’s like strspn() except limits the number of characters checked to n:
size_t strnspn( char const *s, char const *charset, size_t n );
For such fundamental functions, you want to ensure they work correctly in all cases, including degenerate cases, so that if your program has a bug, you can reasonably rule out the bug being in one of those functions. To do that, you need to write unit tests for them.
A Simple Unit Unit Test Framework
In order to write unit tests, a small framework is helpful. Yes, I’m aware of this:
Specifically, there are several open-source, unit-test frameworks for C and they’re probably good; but I wanted something extremely simple. To that end, here’s unit_test.h:
// unit_test.h
#define TEST(EXPR) ( !!(EXPR) || TEST_FAILED( #EXPR ) )
#define TEST_FAILED(EXPR) \
( fprintf( stderr, "%s:%d: " EXPR "\n", prog_name, __LINE__ ), \
!++test_failures )
#define TEST_FUNC_BEGIN() \
unsigned const test_failures_start = test_failures
#define TEST_FUNC_END() \
return test_failures == test_failures_start
To write unit tests, write one function for each “thing” you want to test where “thing” can be a single function or a few related functions:
static bool test_whatever( void ) {
TEST_FUNC_BEGIN();
TEST( whatever( ... ) ) == 42 );
TEST_FUNC_END();
}
If a test fails, the TEST macro will print the stringified expression that didn’t evaluate to true and its line number.
Note that the TEST macro also returns the success of the test so it can be used in a condition since there are some tests that depend on the success of earlier tests. For example, if you’re testing linked-list functions and you expect the front of the list to be the value “A,” you first want to test that the result isn’t NULL:
char const *const result = slist_front( list );
if ( TEST( result != NULL ) )
TEST( strcmp( result, "A" ) == 0 );
Similarly, a test function returns the success of the entire set of tests within the function in case other test functions depend on the success of earlier functions.
To write tests for strnspn, you’d create a file like:
// util_test.c
// local
#include "config.h"
#include "util.h"
#include "unit_test.h"
// standard
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
char const *prog_name;
unsigned test_failures;
static bool test_strnspn( void ) {
TEST_FUNC_BEGIN();
TEST( strnspn( "", "AB", 0 ) == 0 );
TEST( strnspn( "A", "AB", 0 ) == 0 );
TEST( strnspn( "B", "AB", 0 ) == 0 );
TEST( strnspn( "X", "AB", 0 ) == 0 );
TEST( strnspn( "XA", "AB", 0 ) == 0 );
TEST( strnspn( "XB", "AB", 0 ) == 0 );
TEST( strnspn( "XAB", "AB", 0 ) == 0 );
TEST( strnspn( "A", "AB", 1 ) == 1 );
TEST( strnspn( "B", "AB", 1 ) == 1 );
TEST( strnspn( "AB", "AB", 2 ) == 2 );
TEST( strnspn( "BA", "AB", 2 ) == 2 );
TEST( strnspn( "ABA", "AB", 2 ) == 2 );
TEST( strnspn( "ABX", "AB", 2 ) == 2 );
TEST_FUNC_END();
}
int main( int argc, char const *const argv[] ) {
prog_name = argv[0];
test_strnspn();
printf( "%u failures\n", test_failures );
exit( test_failures > 0 );
}
Integrating with Autoconf
To have Autoconf compile your test programs, add the following to src/Makefile.am:
check_PROGRAMS = util_test
util_test_SOURCES = config.h unit_test.h util_test.c
Then if you type make check, your test programs will be compiled at which point you can run them manually. To have make check also run the test programs automatically, also add the following to src/Makefile.am:
TESTS = $(check_PROGRAMS)
Conclusion
Any non-trivial project should include unit tests for all fundamental functions that can be tested independently using stand-alone executables. A simple testing framework helps write tests. Unit tests can be integrated with Autoconf to compile and run them.
There are more parts to come!

Top comments (0)