6.10.2 Source file inclusion
... A preprocessing directive of the form:
#include pp-tokens(that does not match one of the two previous forms) is permitted. The preprocessing tokens afterinclude
in the directive are processed just as in normal text. ... The directive resulting after all replacements shall match one of the two previous forms.
An example. Suppose you have a legacy source file that you want to write some unit tests for. For example:
/* legacy.c */ #include "wibble.h" #include <stdio.h> int legacy(void) { ... info = external_dependency(stdout); ... }
First create a file called
nothing.h
as follows:
/* nothing! */
nothing.h
is a file containing nothing and is an example of the
Null Object Pattern).
Then refactor legacy.c to this:
/* legacy.c */ #if defined(UNIT_TEST) # define LOCAL(header) "nothing.h" # define SYSTEM(header) "nothing.h" #else # define LOCAL(header) #header # define SYSTEM(header) <header> #endif #include LOCAL(wibble.h) /* <--- */ #include SYSTEM(stdio.h) /* <--- */ int legacy(void) { ... info = external_dependency(stdout); ... }
Now structure your unit-tests for legacy.c as follows:
First you write the fake implementations of the external dependencies. Note that the type of
stdout
is not FILE*
.
/* legacy.test.c: Part 1 */ int stdout; int external_dependency(int stream) { ... return 42; }Then #include the source file. Note carefully that we're #including
legacy.c
here
and not legacy.h
/* legacy.test.c: Part 2 */ #include "legacy.c"Then write your tests:
/* legacy.test.c: Part 3 */ #include <assert.h> void first_unit_test_for_legacy(void) { ... assert(legacy() == expected); ... } int main(void) { first_unit_test_for_legacy(); return 0; }
Then compile
legacy.test.c
with the -D UNIT_TEST
option.
This is pretty brutal, but it might just allow you to create an initial seam which you can then gradually prise open. If nothing else it provides a way to create characterisation tests to familiarize yourself with legacy code.
The -include compiler option might also prove useful.
-include file
Process file as if #include "file" appeared as the first line of the primary source file.
Using this you can create the following file:
/* include_seam.h */ #ifndef INCLUDE_SEAM #define INCLUDE_SEAM #if defined(UNIT_TEST) # define LOCAL(header) "nothing.h" # define SYSTEM(header) "nothing.h" #else # define LOCAL(header) #header # define SYSTEM(header) <header> #endif #endif
and then compile with the
-include include_seam.h
option.