Faking C Function with fff.h

Faking C Function with fff.h

By Mike Long

Overload, 23(125):17-19, February 2015


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:






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.