How to write unit tests in plain C?

I've started to dig into the GLib documentation and discovered that it also offers a unit testing framework. But how could you do unit tests in a procedural language? Or does it require to program OO in C?

31.5k 22 22 gold badges 109 109 silver badges 132 132 bronze badges asked Feb 26, 2010 at 17:13 helpermethod helpermethod 61.5k 71 71 gold badges 195 195 silver badges 280 280 bronze badges

9 Answers 9

Unit testing only requires "cut-planes" or boundaries at which testing can be done. It is quite straightforward to test C functions which do not call other functions, or which call only other functions that are also tested. Some examples of this are functions which perform calculations or logic operations, and are functional in nature. Functional in the sense that the same input always results in the same output. Testing these functions can have a huge benefit, even though it is a small part of what is normally thought of as unit testing.

More sophisticated testing, such as the use of mocks or stubs is also possible, but it is not nearly as easy as it is in more dynamic languages, or even just object oriented languages such as C++. One way to approach this is to use #defines. One example of this is this article, Unit testing OpenGL applications, which shows how to mock out OpenGL calls. This allows you to test that valid sequences of OpenGL calls are made.

Another option is to take advantage of weak symbols. For example, all MPI API functions are weak symbols, so if you define the same symbol in your own application, your implementation overrides the weak implementation in the library. If the symbols in the library weren't weak, you would get duplicate symbol errors at link time. You can then implement what is effectively a mock of the entire MPI C API, which allows you to ensure that calls are matched up properly and that there aren't any extra calls that could cause deadlocks. It is also possible to load the library's weak symbols using dlopen() and dlsym() , and pass the call on if necessary. MPI actually provides the PMPI symbols, which are strong, so it is not necessary to use dlopen() and friends.

You can realize many of the benefits of unit testing for C. It is slightly harder, and it may not be possible to get the same level of coverage you might expect from something written in Ruby or Java, but it's definitely worth doing.

30.8k 15 15 gold badges 72 72 silver badges 104 104 bronze badges answered Feb 26, 2010 at 17:40 7,313 3 3 gold badges 31 31 silver badges 34 34 bronze badges

Best answer so far. You even show how to handle the practical need of Dependency Injection in C, which is the primary technique for isolating code-reach to achieve true unit-testing.

Commented Apr 4, 2011 at 14:37

At the most basic level, unit tests are just bits of code that execute other bits of code and tell you if they worked as expected.

You could simply make a new console app, with a main() function, that executed a series of test functions. Each test would call a function in your app and return a 0 for success or another value for failure.

I'd give you some example code, but I'm really rusty with C. I'm sure there are some frameworks out there that would make this a little easier too.

80k 8 8 gold badges 164 164 silver badges 286 286 bronze badges answered Feb 26, 2010 at 17:22 David Hogue David Hogue 1,821 1 1 gold badge 16 16 silver badges 23 23 bronze badges

If you are still looking for a framework try this list for available c unit test frameworks: en.wikipedia.org/wiki/List_of_unit_testing_frameworks#C

Commented Feb 26, 2010 at 20:25 ah-ha excellent list. There's even more frameworks out there than I thought. Commented Feb 26, 2010 at 21:56

You can use libtap which provides a number of functions which can provide diagnostics when a test fails. An example of its use:

#include #include int main () < plan(3); ok(foo(), "foo returns 1"); is(bar(), "bar", "bar returns the string bar"); cmp_ok(baz(), ">", foo(), "baz returns a higher number than foo"); done_testing; > 

Its similar to tap libraries in other languages.

answered May 26, 2011 at 17:11 751 1 1 gold badge 6 6 silver badges 4 4 bronze badges

Here's an example of how you would implement multiple tests in a single test program for a given function that might call a library function.

Suppose we want to test the following module:

#include int my_div(int x, int y) < if (y==0) exit(2); return x/y; >

We then create the following test program:

#include #include #include #include // redefine assert to set a boolean flag #ifdef assert #undef assert #endif #define assert(x) (rslt = rslt && (x)) // the function to test int my_div(int x, int y); // main result return code used by redefined assert static int rslt; // variables controling stub functions static int expected_code; static int should_exit; static jmp_buf jump_env; // test suite main variables static int done; static int num_tests; static int tests_passed; // utility function void TestStart(char *name) < num_tests++; rslt = 1; printf("-- Testing %s . ",name); >// utility function void TestEnd() < if (rslt) tests_passed++; printf("%s\n", rslt ? "success" : "fail"); >// stub function void exit(int code) < if (!done) < assert(should_exit==1); assert(expected_code==code); longjmp(jump_env, 1); >else < _exit(code); >> // test case void test_normal() < int jmp_rval; int r; TestStart("test_normal"); should_exit = 0; if (!(jmp_rval=setjmp(jump_env))) < r = my_div(12,3); >assert(jmp_rval==0); assert(r==4); TestEnd(); > // test case void test_div0() < int jmp_rval; int r; TestStart("test_div0"); should_exit = 1; expected_code = 2; if (!(jmp_rval=setjmp(jump_env))) < r = my_div(2,0); >assert(jmp_rval==1); TestEnd(); > int main()

By redefining assert to update a boolean variable, you can continue on if an assertion fails and run multiple tests, keeping track of how many succeeded and how many failed.

At the start of each test, set rslt (the variables used by the assert macro) to 1, and set any variables that control your stub functions. If one of your stubs gets called more than once, you can set up arrays of control variables so that the stubs can check for different conditions on different calls.

Since many library functions are weak symbols, they can be redefined in your test program so that they get called instead. Prior to calling the function to test, you can set a number of state variables to control the behavior of the stub function and check conditions on the function parameters.

In cases where you can't redefine like that, give the stub function a different name and redefine the symbol in the code to test. For example, if you want to stub fopen but find that it isn't a weak symbol, define your stub as my_fopen and compile the file to test with -Dfopen=my_fopen .

In this particular case, the function to be tested may call exit . This is tricky, since exit can't return to the function being tested. This is one of the rare times when it makes sense to use setjmp and longjmp . You use setjmp before entering the function to test, then in the stubbed exit you call longjmp to return directly back to your test case.

Also note that the redefined exit has a special variable that it checks to see if you actually want to exit the program and calls _exit to do so. If you don't do this, your test program may not quit cleanly.

This test suite also counts the number of attempted and failed tests and returns 0 if all tests passed and 1 otherwise. That way, make can check for test failures and act accordingly.

The above test code will output the following:

-- Testing test_normal . success -- Testing test_div0 . success Total tests passed: 2 

And the return code will be 0.