Testing a function with static global variable in Ceedling

Viewed 1994

How can I write a test in Ceedling for a function that uses static global variable? I would like to test for each possible value of the variable to achieve a good test coverage.

//Pseudo code for file_under_test.c
static int global_var;

int func_under_test(){ 

   switch(global_var){
    case x:
      return some_value;
    case y:
      return some_other_value;
    .
    .
    .
    .
    default:
      return something; 
   }

}
4 Answers

This is a super common problem in unit testing C code and the most common solution I know of is to define the static keyword out of existence when testing. This requires some planning and is hard to do in legacy code but any static that I plan on testing against is replaced by some other string. Usually STATIC or better yet TESTABLE_STATIC.

Remember that ceedling and probable most unit test frameworks sets a compile time macro TEST so you code would be

//Pseudo code for file_under_test.c
#ifdef TEST
#define TESTABLE_STATIC 
#else
#define TESTABLE_STATIC static
#endif


TESTABLE_STATIC int global_var;

int func_under_test(){ 

   switch(global_var){
    case x:
      return some_value;
    case y:
      return some_other_value;
    .
    .
    .
    .
    default:
      return something; 
   }

}

Then in your test file you just treat the variable as a global

// your ceedling test 
#include <your_functions.h>

extern int global_var;
void test_function_under_test(void)
{
    // your test code setting global_var as needed
    global_var = some_test_value;
    TEST_ASSERT_EQUAL(expected_val, func_under_test());
}

I usually hide the TESTABLE_STATIC in a project header file or if you have a datatypes.h file so it is generally available everywhere in my project.

This also works for unit testing your static functions in a translation unit.

This question does not really have anything specifically to do with Ceedling (or Unity, CMock, etc), but I rather think this is an example of interpreting the word "unit" in a very specific way. The short version of my answer is that the example function you have written here does not really constitute a self-contained "unit", so I would claim that this is not really "unit-testable".

Only think of a "function" as a "unit" if it is a pure function or if you can find an appropriate seam (e.g. stubs, spies, or mocks to external interfaces)! Otherwise you would by neccessity need to check for implementation details inside the test, which makes for very brittle tests.

In order to have a "unit" of testable code, you need to both be able to see the effects of the unit under test (e.g. comparing the returned value and/or checking for other side effects) AND to be able to stimulate the unit under test (e.g. by passing arguments into a the function or by first setting up some side effects which the unit under test relies on).

The example function is relying on the side effects of some other function (one which has the side effect of modifying your static "global" variable), so a "proper unit" in this case would need include whatever function you have which triggers these side effects. I suppose your file already has at least one such function, or your example code would never return anything different*.

*This is unless your example actually has the side effect of modifying the static variable itself. In that case there should at least be a function which resets the "global state", otherwise your tests will not be isolated from each other (i.e. it is hard to make them order-independent). A better solution would be to explicitly expose the dependency of your state through the arguments to func_under_test, like func_under_test(struct some_opaque_type *state) and add a struct some_opaque_type *init_for_func_under_test() function.

TL;DR: If your function is not a pure function (e.g. it relies on hidden state and/or has side effects itself) or if you don't have the appropriate "seams" (e.g. stubs or spies), then also include functions which can modify the hidden state or verify the side effects in your definition of unit under test.

I used a wrapper, that includes the orginal C file and adds some helpers for test So you have an unchanged orginal c source, but access to all the needed internals.

file wrapped_for_test.h

#include <file_under_test.h>
void set_for_test(int value);

file wrapped_for_test.c

#include <file_under_test.c>

void set_for_test(int value)
{
  global_var = value;
}

You can create a test helper function in file_under_test.c that will setup the global_var before calling func_under_test(). If needed this test function helper can be compiled only for testing purpose (using specific #ifdef) so that is it not shipped with the rest of the code if your code is build for eg a product.

file_under_test.h:

void set_for_test(int value);

file_under_test.c

#ifdef TESTS
void set_for_test(int value)
{
  global_var = value;
}
#endif

test_file.c:

#include <assert.h>
#include "file_under_test.h"

// some other tests
set_for_test(3);
assert (func_under_test() == something);
//...
Related