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 int
s 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 #include
s 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.
James,
I translated this article into Japanese: http://d.hatena.ne.jp/tdd4ec/20130611/1370904184