Doing C code unit testing on a shoestring: Part 1- The basics and the tools
Test InstrumentationCreating instrumented code out of your production code comprises an intimidating amount of menial labor. It is here where the test automation tools ought to shine. They do, provided they parse your code and (important!) its dependencies correctly.
However, a lot of automation can be accomplished by using clever C macros. To get a taste of it, consider a definition in file scope (i.e., outside of any block)
static short foo;
The problem is to inspect its value, say, before and after executing a test case, without modifying your source file. While this seems impossible, we can do this if we have a macro like this:
#define static /*nothing*/
Note that it is a valid C macro which makes foo a variable with external linkage. Your harness code can now say:
extern short foo;
This trick won't work though for static objects in a block scope: the macro will make foo an automatic. This can be repaired by a variation on the theme:
#define static extern
The new macro turns a definition into a declaration, and it turns out that it is legal in C to put declarations any place you could put a definition. The definition itself,
short foo;
will then go to your test harness file.
Note: This technique won't work as expected if there are several foo in several blocks, whether nested or not, as in
static
short foo;
if
(x)
{
static short foo;
...............
if (y)
{
static short foo;
...............
}
}
else
{
static short foo;
...............
}
Few corporate coding standards, especially in safety-related environment, would tolerate this style better fit for the Obfuscated C Contest. So we can safely disregard this drawback.
Other instrumentation techniques will be demonstrated in the next section. Our concern now will be, where to put the macro responsible for the trick
#define static extern
We do not want this definition visible anywhere except in the unit under test. To achieve this goal, let's do the following.
Assume that there is a header file in your project that is included in every source file. It is more than likely that you have one already; it might hold global project configuration parameters and/or include common goodies like stddef.h and limits.h. Let's say it is called project.h, so all sources have a statement
#include "project.h"
Let's now modify this ubiquitous project.h by adding the following:
#include "instrum.h"
To achieve this variable behavior, we construct "instrum.h" to begin with the following passage:
#ifdef
INSTRUM_HEADER
#
include
INSTRUM_HEADER
#endif
/*INSTRUM_HEADER*/
The idea is that in normal compilation of a source file, INSTRUM_HEADER is not defined and the source compiles as it always did.
When, however, a source file, say foo.c, is the UUT, we create an instrumentation header file for it, say, instrum_foo.h and pass the definition
INSTRUM_HEADER="instrum_foo.h"
on the command line of the compiler. (Usually, it's a "D compiler
switch, or an equivalent configuration in the integrated development
environment. Note that on Windows platforms passing a quoted definition
in the command line is a tricky dealing with CMD.EXE; try
INSTRUM_HEADER="\"instrum_foo.h\"
As we will see later, there is a good chance to use the same instrumentation header for all sources to be unit-tested, say, instrum_common.h. This approach will be our target, and the definition to pass to the compiler is
INSTRUM_HEADER="instrum_common.h"
The instrumentation header, instrum_common.h, will have
#define INSTRUM_STATIC extern
(Or it may have #define INSTRUM_STATIC or no definition for INSTRUM_STATIC at all, depending on the needs.)
The "magic" header, instrum.h, will have the following:
#ifdef
INSTRUM_STATIC
#
define static
INSTRUM_STATIC
#endif
/*INSTRUM_STATIC*/
That is, if instrumentation for static is not defined, the keyword will keep its normal meaning. Otherwise, its meaning becomes whatever the instrumentation header assigns to it; this remains completely transparent to the magic header.
C code instrumentation
Now we are in a position to devise instrumentation of the code by
abusing other keywords.
Instrumenting the if statements. Following the pattern outlined in the previous section, let's add the following to instrum.h:
#ifdef
INSTRUM_IF
#
define
INSTRUM_IF(condition)
#endif
The instrumentation header instrum_common.h would have something like
#define
INSTRUM_IF(condition) \
if (instrum_if(#condition,
(condition)!=0, \
__FILE__, __LINE__,
__FUNCTION__))
extern
int
instrum_if(const
char
*condition_name,
int
condition,
const
char *filename,
int line,
const char
*function_name);
Implementation of instrum_if can be anything you want it to be, except that in order not to alter the behavior of you code, it must return its second argument (condition). For instance, the following implementation just prints what condition where and if it is true or false:
int
instrum_if(const
char
*condition_name,
int
condition,
const char *filename,
int line,
const char
*function_name)
{
printf("Condition
%s in funtion %s"
" (file %s line %d) is %s\n",
condition_name, function_name,
filename, line,
(condition)?"true":"false");
return
condition;
}
This implementation goes to some instrumentation support source file. Of course this file itself should not be instrumented.
Some (pre-C99) compilers might not define __FUNCTION__, or you might not care to output the file name. Your implementation might be like so:
#define
INSTRUM_IF(condition) \
if(instrum_if1(#condition,
(condition)!=0, \
__LINE__))
extern
int instrum_if1(const char
*condition_name,
int condition,
int
line);
with a corresponding implementation.
We will see later that instrumentation of other keywords will require a use of if. The if will come there instrumented but we don't want to see the effects of that instrumentation. So a real instrumentation function for if must detect whether the if is a result of instrumentation of something else and if so suppress all instrumentation actions.
To read Part 2, go to "Toward
code
coverage analysis."
To read Part 3, go to "Building
a unit test framework."
Ark Khasin, PhD, is with MacroExpressions. He can be contacted at
akhasin@macroexpressions.com.


Loading comments... Write a comment