#include Test Double

It’s day one of adding tests to your legacy C code. You get stopped dead when the compiler announces that the code you are coaxing into the test harness can’t be compiled on this machine. You are stuck on the Make it compile step of Crash to Pass.

Moving your embedded legacy C code (embedded C code without tests) into a test harness can be a challenge. The legacy C code is likely to be tightly bound to the target processor. This might not be a problem for production, but for off-target unit testing, it is a big problem.

For C we have a limited mechanisms for breaking dependencies. In my book, I describe at length link-time and function pointer substitutions, but only touch on preprocessor stubbing.

In this article we’ll look at #include Test-Double as a way to break dependencies on a problem #include file.

You have a header file, provided by your silicon vendor, that depends on the target. This is similar to one I ran into last week:

/*
 *  ---- acmetypes.h ----
 */
 
#ifndef _ACME_STD_TYPES
#define _ACME_STD_TYPES
 
#if defined(_ACME_X42)
    typedef unsigned int        Uint_32;
    typedef unsigned short      Uint_16;
    typedef unsigned char       Uint_8;
 
    typedef int                 Int_32;
    typedef short               Int_16;
    typedef char                Int_8;
 
#elif defined(_ACME_A12)
    typedef unsigned long       Uint_32;
    typedef unsigned int        Uint_16;
    typedef unsigned char       Uint_8;
 
    typedef long                Int_32;
    typedef int                 Int_16;
    typedef char                Int_8;
#else
    #error <acmetypes.h> is not supported for this environment
#endif
 
#endif  /* _ACME_STD_TYPES */

The C preprocessor stops when it gets to the #error. You might think you can just define _ACME_X42 or _ACME_A12, but you are testing off-target, and likely on a 64 bit machine (as I am at the time of this writing), and that can lead to problems where int size matters.

It is always best to include the production header file, but sometimes that won’t work, especially in legacy situations. We don’t have to give up though. We can employ a #include Test-Double.

In this case it will be pretty easy. We can define a new header file with the same name, and put it in the include path in front of the production code include. #include Test-Double looks like this:

/*
 * acmetypes.h - test double for off target testing
 */
 
#ifndef ACMETYPES_H_
#define ACMETYPES_H_
 
#include <stdint.h>
 
typedef uint32_t        Uint_32;
typedef uint16_t        Uint_16;
typedef uint8_t         Uint_8;
 
typedef int32_t         Int_32;
typedef int16_t         Int_16;
typedef int8_t          Int_8;
 
#endif /* ACMETYPES_H_ */

Off target you have your development system’s native implementation of stdint.h. It is the standard way to deal with specific sized ints when you must. Above we just redefined the Acme types in therms of the portable types.

If you are using CppUTest and its makefile system, you can direct the makefile to look at your test-double header files before the production code header files, like this:

INCLUDE_DIRS =\
  .\
  include \
  include/* \
  $(CPPUTEST_HOME)/include/ \
  mocks/includes \
  $(ACME_INCLUDES) \

Point $(ACME_INCLUDES) at the silicon vendor dependent include directory. Place the test-double header file in some directory like mocks/includes. As long as mocks/includes is before $(ACME_INCLUDES) in the INCLUDE_DIRS, the test-double will take the place of the production code header file. During the production build mocks/includes will not be part of the include path.

You don’t need to use CppUTest’s makefile system for this. All compilers I am aware of use the include path approach.

To make sure this all works as we expect, we can also add this test case.

extern "C"
{
#include "acmetypes.h"
}
 
#include "CppUTest/TestHarness.h"
 
TEST_GROUP(acmetypes) {};
 
TEST(acmetypes, checkIntSizes)
{
    LONGS_EQUAL(1, sizeof(Uint_8));
    LONGS_EQUAL(1, sizeof(Int_8));
    LONGS_EQUAL(2, sizeof(Uint_16));
    LONGS_EQUAL(2, sizeof(Int_16));
    LONGS_EQUAL(4, sizeof(Uint_32));
    LONGS_EQUAL(4, sizeof(Int_32));
}

There are other situations where a #include stub can be used. For example break a long chain of #includes where you just need a couple symbols. But keep in mind, it is still better to use the production code header when you can.

If this helped, tell me about it. If I’m missing something, tell me that too.

7 thoughts on “#include Test Double

  1. I was wondering if you had any Idea how to do something like this in Visual Studio using C++. I am trying to create a replacement .h file where a class is defined. I have to pass an instantiation of this class to the function I need to test. The production code class uses so much other production code, which in turn includes other production code, that my tests ends up including a large chunk of the whole system. This is legacy code and I am not allowed to change the way the original class works. When I create a new include file with the same name as the production code the only thing it complains about is the redefinition of the class. This leads me to believe that is is seeing and compiling both .h files.

  2. Hello James

    its not working when the header is in the same folder where it is included, or?

    In my case #include “some.h” looks every time first in the same directory and uses fallback the fake. faking works when both are in different folders or when using <> instead of “”.
    Is it true for you and you have a solution without faking every file?

    My best to you

    marco

  3. When I try to mock ctype.h, it seems to cause a problem with the compilation of CppUTest itself:

    In file included from /usr/local/include/c++/11.2.0/bits/localefwd.h:42,
    from /usr/local/include/c++/11.2.0/string:43,
    from /home/cpputest/include/CppUTest/MemoryLeakDetectorNewMacros.h:48,
    from :
    /usr/local/include/c++/11.2.0/cctype:64:11: error: ‘isalnum’ has not been declared in ‘::’
    64 | using ::isalnum;
    | ^~~~~~~
    compilation terminated due to -Wfatal-errors.

  4. The target’s compiler includes a non-standard function toint() in the standard library ctype. 🙁

Leave a Reply

Your email address will not be published. Required fields are marked *

Be gone spammers * Time limit is exhausted. Please reload the CAPTCHA.