I have done some more digging and unearthed in the book by James Grenning "Test Driven Development for Embedded C" at least two solutions to this problem.
- Link-time Substitution
- Function Pointer Substitution
Link-time Substitution:
General summary of this option:
Overall, this seems rather less intuitive to implement and follow and requires a bit of a learning curve. However, the plus side is that you don't need to change anything in your production code which is very important.
In this case, you need to work with makefiles to do the following steps:
Build your production code into a library
Be sure to separate your tests into different files so that tests that need to use a certain function as a mock are in separated from the tests that need to use the original implementation of that same function.
With the make files you will need to make micro builds of parts of your code and finally combine them all together. As an example, for a specific function that you want to both mock and use the original implementation in different tests that are separated in, let's say, two files (test1.cpp contains mock implementation of func A1 and test2.cpp contains original implementation of function A1) you will do:
- First, build test1.cpp together with the library of the production code and WITHOUT test2.cpp. The mock function will take preference during link time.
- Second, build test2.cpp together with the library of the production code and WITHOUT test1.cpp. The original implemenation in the library of function A1 will take preference during link time (as it is the only one there).
- combine the two binaries created together into one executable.
Now these three steps are just a high-level explanation. I know it is not ideal but it is still worth something. I admit I didn't do it myself but I did read about it James Grenning's book and if you would like, he explains it in more detail in Appendix 1 (titled Development System Test Environment) in his book and you may see the makefile structure he used in his book-code-example here: https://pragprog.com/titles/jgade/test-driven-development-for-embedded-c/
Function Pointer Substitution:
General summary of this option:
This is much more intuitive and simple to implement. However, the downside is that it requires a subtle change to your production code where your function is declared and defined.
Let's say you want to mock a function called A1 that is defined in the file Production.c file as:
//Production.c
int A1(void)
{
... original implementation written here
}
and in Production.h it is declared as:
//Production.h
int A1(void);
so you may change the function declaration and definition this way:
//Production.c
int A1_original(void)
{
... original implementation written here
}
int (*A1)(void) = A1_original;
and in Production.h it is declared as:
//Production.h
extern int (*A1)(void);
#ifdef TDD_ENABLED // use ifdef with TDD_ENABLED which is defined only in unit test project. This is because you want to declare the original implementation function as a public function so that you can freely assign it to the function pointer A1 in the test files.
int A1_original(void);
#endif
Now, for each test in which you want to use the original function implementation simply call it the same way you would before the change:
A1();
As you can see, this means that also, throughout your production code you don't need to change the way the function is called.
This is also true to your testing files.
Now, in case you want to use a mock for this function you simply do the following:
//Test1.cpp
int Fake_A1(void)
{
... fake function implementation
}
TEST(test_group_name,test_name)
{
int (*temp_holder)(void) = A1; // hold the original pointer in a temp pointer
A1 = Fake_A1; // assign A1 to call the mock function
... run all the test here
A1 = temp_holder; // assign A1 to call the original function back again so that the mock function is used only in the scope of this test
}
Ideally, if you intend to have multiple such tests you would make the assignment to the mock and reassignment to the original function while using a class with Setup()
and Teardown()
and using a test fixture (TEST_F
) like this:
//Test1.cpp
class A1_Func_Test : public ::testing::Test
{
protected:
int (*temp_holder)(void) = A1; // hold the original pointer in a temp pointer
virtual void SetUp()
{
A1 = Fake_A1; // assign A1 to call the mock function
}
virtual void TearDown()
{
A1 = temp_holder; // assign A1 to call the original function back again so that the mock function exists only in the scope of this test
}
};
TEST_F(A1_Func_Test , Test1_A1)
{
write tests here...
}
TEST_F(A1_Func_Test , Test2_A1)
{
write tests here...
}
How to implement function pointer substitution with FFF Mock Framework:
After following the instructions written above, the same changes should be made to your production code files (Production.c and Production.h).
However, for your unit test files you simply do the following in case you want to mock the function (if you want to test the function without mocking it then just call it regularly):
//Test1.cpp
//Using FFF Mocking framework:
DEFINE_FFF_GLOBALS;
FAKE_VALUE_FUNC(int, A1_mock);
int A1_custom(void)
{
write code here for mock function implementation...
}
TEST(test_group_name,test_name)
{
int (*temp_holder)(void) = A1; // hold the original pointer in a temp pointer
// setting customized mock function for this test
A1_mock_fake.custom_fake = A1_custom;
A1 = A1_mock;
// simple example of a test using the the FFF framework:
int x;
x = A1();
ASSERT_EQ(A1_mock_fake.call_count, 1);
// assign A1 to call the original function back again so that the mock function exists only in the scope of this test
A1 = temp_holder;
RESET_FAKE(A1_mock); // reset all parameters of the mock function used so when used in a subsequent test we will start "clean"
}
Summary
I believe this answers the questions on how to go about and do what I had asked.