Test Seams in C: Function pointers vs. Preprocessor hash defines vs. Link-time substitution

When replacing production C code with test doubles (or fakes), there are three basic approaches: function pointers, preprocessor hash defines, and link time-time substitution.  All three are examples of what Michael Feathers calls a seam, or a way to change the behavior of a program without editing in that place. We use seams when we unit test software to decouple and localise the software under test.  Each of these types of seams has different trade-offs for testing embedded C.  Here’s what I think.

Function Pointers (run-time substitution)

  • + Easy to substitute at runtime for test functions
  • + Makes for more decoupled design
  • – Harder for IDE to jump to definitions, etc.
  • – Can make static code analysis mot work so well

Preprocessor hash defines (preprocessor-time substitution)

  • + Good for getting legacy code under test
  • – Needs discipline to structure the hash defines well
  • – Can make code harder to read and understand
  • – Definitions can be hard to locate because they can be set on the command line (-D), include path, header files, etc and can be overridden at any location

Link different function implementations (link-time substitution)

  • + Good for getting legacy C under test (limited changes needed to production code)
  • – Need to check the link definitions to understand what is running for any given executable
  • – Can’t have multiple implementations of a function in one executable
  • – Can create the need for multiple test executables

You can use the fake function framework to generate the fake functions for testing, whatever type of seam you choose.  It just creates a function and the supporting structure to capture arguments and provide return values. You can find more information about the fake function framework here:

https://github.com/meekrosoft/fff#readme

10 Comments

Filed under Uncategorized

10 responses to “Test Seams in C: Function pointers vs. Preprocessor hash defines vs. Link-time substitution

  1. Your github link points to the slb.com OWA login page.

  2. Personally I use combination of 2 and 3: I simply include tested file in the unit test and re-define mocked function with help of Google Mock. Something like this:

    [foo.c]
    void foo(int size)
    {
    void * p = malloc(size);
    strcpy(p, “hello world\n”);
    }

    [foo-test.cpp]
    class FreeFunctionHooks
    {
    MOCK1(malloc, void *(size_t));
    };

    #define INIT_HOOKS() FreeFunctionHooks hooks

    #define malloc(x) hooks.malloc(x)
    #include “foo.c”
    #undef malloc

    TEST()
    {
    INIT_HOOKS();

    char buffer[100];
    EXPECT_CALL(hooks, malloc(_))
    .WillRepeatedly(ReturnPointee(buffer));
    foo(42);
    ASSERT_STREQ(“hello world\n”, buffer);

    EXPECT_CALL(hooks, malloc(31337))
    .WillOnce(ReturnNull());
    foo(31337);
    }

    As far as I can see, it has all listed pros and none of the cons :-)

    • meekrosoft

      Yes, this is similar to what we did on our last project, I like it a lot, except we used C and fff.h. The only downside to this is if you want to mock a function in one test suite but use the production function in another you have to have separate executables or there will be linker errors. Thanks for sharing!

      • meekrosoft

        Ah, I just realised that hash defining the function name will make it so the link will work. Nice, I like this combination. Cheers Yury!

    • ykrons

      Hi,

      Very interesting way to use the hashed function name, but I have a question. In such case, foo.c will probably include some #include lines (ie: stdlib.h). How did you manage the conflict with malloc for example?

  3. Stephen E.

    Is there a discussion forum for fff? I have some questions.

    • meekrosoft

      If you have an issue you can report it on https://github.com/meekrosoft/fff/issues or if you just want to ask some questions you can ping me on twitter.

      • Stephen E.

        Mike, I tried pinging on twitter — not sure if you got it or not.

        I am working with legacy code, and I want to make minimal changes to it. However, I need the flexibility of function pointers so that I can change the fake function for each unit test group, preferably setting up a fake and tearing it down for each unit test group.

        Is it possible to use function pointers for run-time substitution without changing the declaration of the code under test to a function pointer? I am hoping that fff could do that for me, but I am not sure.

        If fff can do this for me, can you provide an example of how to do it?

        Thanks for your informative blog posts!

  4. meekrosoft

    Hi Stephen,
    I didn’t get a message from you on twitter, what is your twitter username? If you send me a small example test with production code I can try to help you get it under test.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s