Embed from Getty Images
Unit testing C code generally has some significant challenges when compared to some other languages. The main difficulty is that it can be quite difficult to test the outcome of a function call and how it has interacted with other modules and functions. This is because the state of the module is not always accessed (if not stored), or can be complicated to predict if there is a lot of logic.

What can make your code easier to test is if you conceptually break it down into manageable layers which makes it possible to replace normally called functions that are out of the scope of the test? This is normally done with test stubs, fakes, or mocks (conceptually similar test devices). These test devices can be designed or configured to record how they get called and can be set to return predetermined values to the callers, which makes it possible to accurately measure the outcome of your tests.

Braking Dependencies

Michael Feather coined the test “seam” concept in his book Working Effectively with Legacy Code. In C, there are not many options for seams. Two of the more practical choices in C programming are:

  • “Link Seams”, where one tends to compile a subsection of the entire code base you are testing, and replaces modules with similarly named modules containing test stubs, mocks or fakes.
    • The advantage of this is that it forces you to keep your modules small and well designed so that they are testable,
    • The disadvantages of this are that until you have tools designed to support them all, the number of test executables that result from getting each code module in a large code base under test is not easy to manage and is not scalable. For example, with Eclipse CDT, one has few options but to have one test project per module being tested. Sometimes one can combine multiple modules under one test executable, but this can make it difficult to catalogue where a specific module is being tested.
  • “Function Pointer Seams”, where one replaces direct calls to functions with calls using function pointers. This enables one to break dependencies by replacing  functions out of the scope of your tests with your own stubs, fakes or mocks.
    • The advantage of this is that it allows you to keep the test code all in one place. It also gives you a lot of flexibility to replace any function as required. It is also very portable between different systems.
    • Disadvantages are that each function pointer uses RAM (something firmware programmers cannot always afford to use up), the code runs slightly slower, and the source code becomes cluttered with the extra code defining function pointers (making it less readable). Function pointers also make it difficult to build call trees of your code when using static analysis tools.
    • Generally, the much larger test projects that are possible when using this method take a lot longer to compile. For a large code base, you may still want to break your code up into two or smaller logical segments. This can be a consideration for other languages apart from C.

Removing the disadvantages

Embed from Getty Images

I will explain a simple pattern that removes most of the disadvantages of the function pointer methodology.

Normally a function pointer is defined as follows:

void function_to_replace (int input, int* output);
void (*my_replacable_function_ptr)(int input, int* output)=function_to_replace;

void function_to_replace (int input, int* output)
{
//some code
}

and one calls this code as follows:

int main(void)
{
int output;

my_replacable_function_ptr(1, &output);

return EXIT_SUCCESS;
}

If other modules need to access this function, one needs to expose the function pointer as a global in the module’s header file as follows:

extern void (*my_replacable_function_ptr)(int input, int* output);

As suggested earlier, in production code you usually don’t want to use function pointers. To get the best of both worlds, (where functions are replaceable in test code but don’t have the associated overheads and transparency issues in production code) you need to resort to the use of macros. For test code, you want to expose the function pointers, but in production code, you want to expose your functions as direct calls.

The way this is done is as follows. In a commonly exposed header file (present in most projects, let’s assume its called defines.h), you need to define the following macros with a conditional compile:

#ifdef TESTING

# define PROT_F(return_type, function_name, parameter_def) \
extern return_type (*function_name)parameter_def \

# define PROC_F(return_type, function_name, parameter_def) \
return_type def_##function_name parameter_def; \
return_type (*function_name) parameter_def = def_##function_name; \
return_type def_##function_name parameter_def
#else
# define PROT_F(return_type, function_name, parameter_def) \
return_type function_name parameter_def

# define PROC_F(return_type, function_name, parameter_def) \
return_type function_name parameter_def
#endif

When declaring functions that need to be replaceable you need to define them as follows:

PROC_F( void, function_to_be_replaced, (int input, int* output))
{
//some unwanted code
}

And your prototype needs to be declared as follows:

PROT_F( void, function_to_be_replaced, (int input, int* output));

Notice how similar the code looks to the original, and once you get used to it, it is still very readability. When you compile for testing, you simply need to build with  “TESTING” defined.

Assuming that in your test code you want to replace 

void test_stub (int input, int* output)
{
//some code that doesn't do very much
}

You can do the replacement in the following way:

function_to_be_replaced=test_stub;

… and anywhere that the code being tested calls function_to_be_replaced, it will land up calling test_stub instead.

Conclusion

Embed from Getty Images

You should obviously, as a matter of principle restore the functions that have been replaced to their original state after each test. This is to avoid your code being affected in any unanticipated ways by other tests.

Although there is a small readability penalty, I believe this is a small compromise when you consider the very much improved flexibility, manageability and scalability that you will gain in your testability.

If you have further ideas or considerations to offer when it comes to making large C code bases more testable without the associated compromises, I would be very interested to hear your thoughts.

photo credit: Sunflower & Bee via photopin (license)


10 Comments

Paul Lee · March 17, 2016 at 11:02 am

Have you tried using CMock? I find it is much easier with auto-generate the mock than manually writing mock functions.

Firmware Programming by Bryan Jarmain · March 17, 2016 at 4:14 pm

@Paul Lee, Thanks for the suggestion. It looks like a fascinating framework to try. I haven’t got into mocking yet, since it generally requires so many dependencies. But intend to start experimenting with it soon, with ways to reduce these dependencies.

Paul Lee · March 18, 2016 at 8:37 am

I guess is dependent in how the code is structured to get rid of the dependencies. It is much easier to do it in C++ than C as you can easily apply the SOLID principles. For C, possibly could use an event driven framework similar to QP so the main interface is the queue. Miro describes SOLID in his blog (http://embeddedgurus.com/state-space/2012/06/rtos-tdd-and-the-o-in-the-s-o-l-i-d-rules/). In the event driven framework at work, I expose the state when performing a unit test and treat the state handler as a function. This seems to work nicely when it comes to unit test. Ceedling also has a gcov plugin to determine the code coverage of the test, and generates XML results which fits nicely with Jenkins. My next aim is to complement the unit test with a system test. I like this idea (http://www.slideshare.net/ItamarHassin/embedded-software-development-using-bdd) as this describes the test case in a high level syntax. I don’t think this will be hard to develop using python behave just need to have nice API layer.

    Bryan Jarmain · March 18, 2016 at 9:12 pm

    Paul, the actor model is a really nice concept and allows a great deal of efficiency, takes encapsulation to a completely new level, an very elegantly avoids contention.
    I have worked with some code built using QM, and once I got used to it, I found it quite easy to live with.
    Challenges are that it requires quite a mind shift, not always easily imposed on an established system, or coders that are used to working in a more conventional way.
    I have not been happy by what I have seen so far with some aspects of object orientated C – it gets quite hackish when one starts to attempt inheritance, too ugly and risk prone – just program in C++ if you need OO. Lightweight (without inheritance) objects in C can be tempting and useful at times, but its debatable if it plays into the strengths of C, and possibly obfuscates the code. Probably easy to overuse.
    BTW I have tended to avoid requiring code generators to assist with writing unit tests. It adds extra dependencies, restricts your workflow and tools, and locks you in in another way.
    Anyhow thanks for noting these tools – it should be great to try out…

Massimo Manca · March 20, 2016 at 5:05 pm

There are better tools to mock C functions.

Anyway if you don’t wont to use a mock library you could define the functions to mock as weak functions and define another instance of the same function. The last will win, so the last will be your mock.

Using GCC there is also the possibility to mock functions at link stage.

For your reference:
https://en.wikipedia.org/wiki/Weak_symbol

http://stackoverflow.com/questions/13961774/gnu-gcc-ld-wrapping-a-call-to-symbol-with-caller-and-callee-defined-in-the-sam

    Bryan Jarmain · March 20, 2016 at 6:31 pm

    @Massimo Manca, that’s true and it can be useful, so thanks for adding the suggestion. One thing that has recently made me like function pointers is that it offers the flexibility to temporarily replace functions and then later switch the originals back – all at runtime, without re-linking.

      Massimo Manca · March 20, 2016 at 6:45 pm

      @Bryan excuse me but you mentioned a defines.h include file using inside the preprocessor conditionals to compile the right macros. So how do you expect to switch back to the originals all at runtime? Seems to me you need to recompile your files anyway. No advantage compared with weak functions that at least doesn’t add unnecessary complexity calling the function using a pointer to the function. And you don’t need more RAM to use a function pointer if you declare it const.

Bryan Jarmain · March 20, 2016 at 8:05 pm

@Massimo, the conditionals only switch the pointers in when running your unit tests. Otherwise, they are normal function calls. Your unit test saves the original states of the pointers as they are modified during the test, and recovers them during the teardown phase. Also, remember that “weak” isn’t completely portable, and has the problem of requiring a separate link for each layer of testing – yes, I have considered using it as well.

Avoid the abuse of Function Pointers in C - Firmware programming · December 31, 2017 at 3:09 pm

[…] of using function pointers to improve encapsulation, to make your code more maintainable, and to facilitate unit testing. I have also stressed that knowing when not to use function pointers is as important as knowing […]

How to Scale C Unit Testing – Firmware Programming · July 14, 2018 at 6:01 pm

[…] This article has moved. Please visit it by clicking here. […]

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: