Faking functions for testing in C can ease testing. Mike Long overviews a micro-framework for mocking.
I have a little micro-framework called fff.h for generating fake functions (sometimes these types of functions are called mocks) in C. The basic premise is that testing a C source file is difficult in idiomatic C because of all the external function calls that are hardwired into the production code. The way fff.h helps is to make it a one-liner to create fake implementations of these for the purposes of testing.
Let me give an example. In my last C project, the basic formula for testing a C module was like Listing 1.
extern "C" { #include "driver.h" #include "registers.h" } #include "../../fff.h" #include <gtest/gtest.h> extern "C" { static uint8_t readVal; static int readCalled; static uint32_t readRegister; uint8_t IO_MEM_RD8(uint32_t reg) { readRegister = reg; readCalled++; return readVal; } static uint32_t writeRegister; static uint8_t writeVal; static int writeCalled; void IO_MEM_WR8(uint32_t reg, uint8_t val) { writeRegister = reg; writeVal = val; writeCalled++; } } TEST(Driver, When_writing_Then_writes_data_to_DRIVER_OUTPUT_REGISTER) { driver_write(0x34); ASSERT_EQ(1u, writeCalled); ASSERT_EQ(0x34u, writeVal); ASSERT_EQ(DRIVER_OUTPUT_REGISTER, writeRegister); } TEST(Driver, When_reading_data_Then_reads_from_DRIVER_INPUT_REGISTER) { readVal = 0x55; uint8_t returnedValue = driver_read(); ASSERT_EQ(1u, readCalled); ASSERT_EQ(0x55u, returnedValue); ASSERT_EQ(readRegister, DRIVER_INPUT_REGISTER); } |
Listing 1 |
Now, this is a simple example but it illustrates the method. As you can see, most of the test code is taken up writing the fake functions and their associated data members. When modules have many dependencies, I would find myself having to write hundreds of lines of code to get anything compiled and ready to test (did I forget to mention this was legacy code :-)).
After a while of doing this, I started to see a pattern appear. Nearly every fake followed the same pattern, and they all needed to capture the same information. Around this time, Jon Jagger had written some interesting blog posts on using the C preprocessor to do fun things like count. It got me wondering, could I generate the fake code I wanted with the preprocessor?
I played around with different approaches, but soon I had something basic working - I could write a macro that would generate a fake function. After a bit of extra help from Tore Martin Hagen and Jon Jagger I had generalized it to be able to count the number of arguments it would require, and generate the correct code at compile time. A few more iterations, and Listing 2 is the updated code.
extern "C"{ #include "driver.h" #include "registers.h" } #include "../../fff.h" #include <gtest/gtest.h> DEFINE_FFF_GLOBALS; FAKE_VOID_FUNC(IO_MEM_WR8, uint32_t, uint8_t); FAKE_VALUE_FUNC(uint8_t, IO_MEM_RD8, uint32_t); class DriverTestFFF : public testing::Test { public: void SetUp() { RESET_FAKE(IO_MEM_WR8); RESET_FAKE(IO_MEM_RD8); FFF_RESET_HISTORY(); } }; TEST_F(DriverTestFFF, When_writing_Then_writes_data_to_DRIVER_OUTPUT_REGISTER) { driver_write(0x34); ASSERT_EQ(1u, IO_MEM_WR8_fake.call_count); ASSERT_EQ(0x34u, IO_MEM_WR8_fake.arg1_val); ASSERT_EQ(DRIVER_OUTPUT_REGISTER, IO_MEM_WR8_fake.arg0_val); } TEST_F(DriverTestFFF, When_reading_data_Then_reads_from_DRIVER_INPUT_REGISTER) { IO_MEM_RD8_fake.return_val = 0x55; uint8_t returnedValue = driver_read(); ASSERT_EQ(1u, IO_MEM_RD8_fake.call_count); ASSERT_EQ(0x55u, returnedValue); ASSERT_EQ(IO_MEM_RD8_fake.arg0_val, DRIVER_INPUT_REGISTER); } |
Listing 2 |
Now, you might think that there is not much difference between the two options, and you are correct. By creating the Fake Function Framework I can only save 20% less code, big deal. But that misses a few points:
- Every fake is defined in a standard way
- Fakes can be defined in C or C++ file with correct extern wrappers
- More complex dependencies save more coding
And beyond that, there are a bunch of additional features you get when you define your fakes using fff.h :
Function call history with arguments
Say you want to test that a function calls f
unctionA
, then
functionB
, then
functionA
again, how would you do that? Well
fff.h
maintains a call history and also stores the history of function arguments so that it is easy to assert these expectations.
Listing 3 shows how it works.
TEST_F(DriverTestFFF, Given_revisionB_device_When_initialize_Then_enable_peripheral_before_initial izing_it) { // Given IO_MEM_RD8_fake.return_val = HARDWARE_REV_B; // When driver_init_device(); // Then // Gets the hardware revision ASSERT_EQ((void*) IO_MEM_RD8, fff.call_history[0]); ASSERT_EQ(HARDWARE_VERSION_REGISTER, IO_MEM_RD8_fake.arg0_history[0]); // Enables Peripheral ASSERT_EQ((void*) IO_MEM_WR8, fff.call_history[1]); ASSERT_EQ(DRIVER_PERIPHERAL_ENABLE_REG, IO_MEM_WR8_fake.arg0_history[0]); ASSERT_EQ(1, IO_MEM_WR8_fake.arg1_history[0]); // Initializes Peripheral ASSERT_EQ((void*) IO_MEM_WR8, fff.call_history[2]); ASSERT_EQ(DRIVER_PERIPHERAL_INITIALIZE_REG, IO_MEM_WR8_fake.arg0_history[1]); ASSERT_EQ(1, IO_MEM_WR8_fake.arg1_history[1]); } |
Listing 3 |
Of course, if you wish to control how many calls to capture for argument history you can override the default by defining it before include the fff.h like this:
// Want to keep the argument history for 13 calls #define FFF_ARG_HISTORY_LEN 13 // Want to keep the call sequence history for // 17 function calls #define FFF_CALL_HISTORY_LEN 17 #include "../fff.h"
Function return value sequences
Often in testing we would like to test the behaviour of sequence of function call events. One way to do this with fff is to specify a sequence of return values with for the fake function. It is probably easier to describe with an example (see Listing 4).
// faking "long longfunc();" FAKE_VALUE_FUNC(long, longfunc0); TEST_F(FFFTestSuite, return_value_sequences_exhausted) { long myReturnVals[3] = { 3, 7, 9 }; SET_RETURN_SEQ(longfunc0, myReturnVals, 3); ASSERT_EQ(myReturnVals[0], longfunc0()); ASSERT_EQ(myReturnVals[1], longfunc0()); ASSERT_EQ(myReturnVals[2], longfunc0()); ASSERT_EQ(myReturnVals[2], longfunc0()); ASSERT_EQ(myReturnVals[2], longfunc0()); } |
Listing 4 |
By specifying a return value sequence using the
SET_RETURN_SEQ
macro, the fake will return the values given in the parameter array in sequence. When the end of the sequence is reached the fake will continue to return the last value in the sequence indefinitely.
Custom return value delegate
You can specify your own function to provide the return value for the fake. This is done by setting the
custom_fake
member of the fake. Listing 5 is an example.
#define MEANING_OF_LIFE 42 long my_custom_value_fake(void) { return MEANING_OF_LIFE; } TEST_F(FFFTestSuite, when_value_custom_fake_called_THEN_it_returns_custom_return_value) { longfunc0_fake.custom_fake = my_custom_value_fake; long retval = longfunc0(); ASSERT_EQ(MEANING_OF_LIFE, retval); } |
Listing 5 |
Under the hood
So how does this all work under the hood? Let’s take a look at an example:
// faking "long longfunc(long argument);" FAKE_VALUE_FUNC(long, longfunc0, long);
This expands to create a function declaration with its associated capture variables, and a function definition (see Listing 6).
#define FAKE_VALUE_FUNC1(RETURN_TYPE, \ FUNCNAME, ARG0_TYPE) \ DECLARE_FAKE_VALUE_FUNC1(RETURN_TYPE, \ FUNCNAME, ARG0_TYPE) \ DEFINE_FAKE_VALUE_FUNC1(RETURN_TYPE, \ FUNCNAME, ARG0_TYPE) \ |
Listing 6 |
These macros can be used separately if you want to put the declarations in a header file and definitions in a sharable module.
In the declaration macro, we declare a struct and a function with C linkage (see Listing 7).
#define DECLARE_FAKE_VALUE_FUNC1(RETURN_TYPE, \ FUNCNAME, ARG0_TYPE) \ EXTERN_C \ typedef struct FUNCNAME##_Fake { \ DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ DECLARE_ALL_FUNC_COMMON \ DECLARE_VALUE_FUNCTION_VARIABLES \ (RETURN_TYPE) \ RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0); \ } FUNCNAME##_Fake;\ extern FUNCNAME##_Fake FUNCNAME##_fake;\ void FUNCNAME##_reset(); \ END_EXTERN_C \ |
Listing 7 |
The implementation of the fake function is defined with the macro in Listing 8.
#define DEFINE_FAKE_VALUE_FUNC1(RETURN_TYPE, \ FUNCNAME, ARG0_TYPE) \ EXTERN_C \ FUNCNAME##_Fake FUNCNAME##_fake;\ RETURN_TYPE FUNCNAME(ARG0_TYPE arg0){ \ SAVE_ARG(FUNCNAME, 0); \ if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ SAVE_ARG_HISTORY(FUNCNAME, 0); \ }\ else{\ HISTORY_DROPPED(FUNCNAME);\ }\ INCREMENT_CALL_COUNT(FUNCNAME); \ REGISTER_CALL(FUNCNAME); \ if (FUNCNAME##_fake.custom_fake) return \ FUNCNAME##_fake.custom_fake(arg0); \ RETURN_FAKE_RESULT(FUNCNAME) \ } \ DEFINE_RESET_FUNCTION(FUNCNAME) \ END_EXTERN_C \ |
Listing 8 |
Counting with the preprocessor
The curious among you might be wondering about how the preprocessor does counting. Well, the macros are in Listing 9.
#define PP_NARG_MINUS2(...) PP_NARG_MINUS2_(__VA_ARGS__, PP_RSEQ_N_MINUS2()) #define PP_NARG_MINUS2_(...) PP_ARG_MINUS2_N(__VA_ARGS__) #define PP_ARG_MINUS2_N(returnVal, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, N, ...) N #define PP_RSEQ_N_MINUS2() 19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0 #define FAKE_VALUE_FUNC(...) FUNC_VALUE_(PP_NARG_MINUS2(__VA_ARGS__), __VA_ARGS__) #define FUNC_VALUE_(N,...) FUNC_VALUE_N(N,__VA_ARGS__) #define FUNC_VALUE_N(N,...) FAKE_VALUE_FUNC ## N(__VA_ARGS__) |
Listing 9 |
You can learn more about this technique in the resources section at the end of this article.
Summary
The goal of the Fake Function Framework is:
- to make it easy to create fake functions for testing C code
- to be simple – you just download a header file and include include it in your project, there are no fancy build requirements or dependencies of any kind
- to work seamlessly in both C and C++ test environments.
Acknowledgements
The fake function framework would not exist as it does today without the support of key people. Tore Martin Hagen (and his whiteboard), my partner-in-crime in Oslo, was instrumental during the genesis of fff. Jon Jagger, who during ACCU 2011 helped me teach the preprocessor to count. James Grenning, who convinced me the value of global fakes, sent me a prototype Implementation, and showed me how expressive a DSL can be. Micha Hoiting helped me to add support for const arguments. Thanks to you all!
Resources
If you have any questions, drop me a line on twitter @meekrosoft.
To learn more you might want to check out some of these resources:
- The project has lots of example code and documentation on Github – https://github.com/meekrosoft/fff
- James Grenning explains how to fake an RTOS with fff.h – http://www.renaissancesoftware.net/blog/archives/303
- Strongminds blog introduction – http://blog.strongminds.dk/post/2012/08/17/Faked-Function-Framework.aspx
- C macro magic - PP_NARG - http://jonjagger.blogspot.co.uk/2010/11/c-macromagic-ppnarg.html