Let’s say you have code that in some test cases you want the real code and other test cases you want a test double. In C, using link-time binding, you only get to have a single function with a specific name.
I’d suggest that most the time this comes up you want to directly test some module (or function) and you want a substitute when testing code that depends on the module’s interface.
What options do you have?
- Create multiple test runners, where one test runner would use the real function and the other test runner would use the test double.
- Function pointer substitution.
#include
the .c file in anamespace
in the test file.- Linker wrapping.
- Weak linking.
Multiple test runners
This might be a decent solution. Though it can lead to needing many test runners.
Function pointer substitution
Changing a direct function call to an indirect function call is a small change, and the compiler and linker will make sure you get it right. There are a couple examples in my book, in chapter nine. Here’s one of them.
// RandomMinute.h -- original
int RandomMinute_Get(void);
// RandomMinute.c -- original
int RandomMinute_Get(void)
{
return bound - rand() % (bound * 2 + 1);
}
// RandomMinute.h - revised
extern int (*RandomMinute_Get)(void);
// RandomMinute.c - revised
static int __randomMinute_Get(void)
{
return bound - rand() % (bound * 2 + 1);
}
int (*RandomMinute_Get)(void) = __randomMinute_Get;
That refactor is pretty easy, but you must make sure all users of RandomMinute_Get()
include the header file. If you happen to be lazy about calling functions without having them advertised in a header file, the caller will think this is a direct function call. Bad things will happen.
In the test group file, write FakeRandomMinute_Get()
.
//Test file for SomethingThatUses_RandomMinute_Get
extern "C"
{
static randomMinuteValue;
static randomMinuteIncrement;
static void FakeRandomMinute_Init(int first, int increment)
{
randomMinuteValue = first;
randomMinuteIncrement = increment;
}
static int FakeRandomMinute_Get(void)
{
int value = randomMinuteValue;
randomMinuteValue += randomMinuteIncrement;
return value
}
}
in setup()
override RandomMinute_Get()
with FakeRandomMinute_Get()
.
//Test file for SomethingThatUses_RandomMinute_Get - Continued
TEST_GROUP(SomethingThatUses_RandomMinute_Get)
{
void setup()
{
//other setup
FakeRandomMinute_Init(-30, 3);
UT_PTR_SET(RandomMinute_Get, FakeRandomMinute_Get);
}
}
void teardown()
{
}
}
UT_PTR_SET
overrides the function pointer and then restores its original value after teardown()
. Your code under test now has predcitable random minutes.
In another test group, the production RamdomMinute_Get()
function can be tested without overriding the function pointer.
// RandomMinuteTest.cpp
TEST_GROUP(RandomMinute) {}
TEST(RandomMinute, GetIsInRange)
{
for (int i = 0; i < 100; i++)
{
minute = RandomMinute_Get();
AssertMinuteIsInRange();
}
}
But maybe you don’t want to use a function pointer. You might prefer this next solution.
#include
the .c file in a C++ namespace
In this solution, the test-double’s function name in FakeRandomMinute.c
is the same as the function being faked, business as usual for link-time substitution.
// FakeRandomMinute.h
#include "RandomMinute.h"
void FakeRandomMinute_Init(int first, int increment);
// FakeRandomMinute.c
#include "FakeRandomMinute.h"
void FakeRandomMinute_Init(int first, int increment)
{
randomMinuteValue = first;
randomMinuteIncrement = increment;
}
int RandomMinute_Get(void)
{
int value = randomMinuteValue;
randomMinuteValue += randomMinuteIncrement;
return value
}
To test the production implementation, the the RandomMinute.c
file can be #include
d in the test file. In addition, the #include
needs to be wrapped in a C++ namespace
to keep the name out of the global namespace.
// in your test file
namespace Test
{
#include "RandomMinute.c"
}
In the test cases, make calls to Test::RandomMinute_Get()
.
TEST(RandomMinute, GetIsInRange)
{
for (int i = 0; i < 100; i++)
{
minute = Test::RandomMinute_Get();
AssertMinuteIsInRange();
}
}
Linker Wrapping
The linker tricks are not generally available except from gcc. I’ve experimented with wrapping in gcc., but could not find it in visual studio or IAR (at the time I looked).
In gcc, wrap RamdomMinute_Get()
with these command line options:
gcc -Wl,--wrap=RamdomMinute_Get ...
The linker will redirect calls to RamdomMinute_Get()
to __wrap_RamdomMinute_Get()
. You can get at there production implementation by calling __real_RamdomMinute_Get()
. You need to provide __wrap_RamdomMinute_Get()
to hold your fake implimentation and to keep the linker happy.
int __wrap_RamdomMinute_Get()
{
// some fake implementation
}
Maybe some test cases want the real RamdomMinute_Get()
, you could make the wrapped function default to the real with function.
static int (*customRamdomMinute_Get)() = 0;
int __wrap_RamdomMinute_Get()
{
if (customRamdomMinute_Get)
return customRamdomMinute_Get();
return __real_RamdomMinute_Get();
}
I chose to use a function pointer for test specific behavior. This lets you have a generic fake implemention that is customized close to your test case.
There are some limitations so you may want to look at this Stack Overflow article for linker wrapping
Weak Linking
I’d say stay away from weak linking as it means adding non-standard stuff to your producion C code.
Finally
You can mix and match these techniques. Let me know how this works for you or if you know some other solutions to this problem.