Doing C code unit testing on a shoestring: Part 2- Code coverage analysis
Assume for a moment that all of the functions in your program arewritten using only if/else statements to control program flow. How doyou test it?
A test achieves 100% code coverage if and only if every if statementwas executed and at least once the condition was false and at leastonce the condition was true (somepeople call this “branch coverage” ).
If you always strive for 100% coverage (
If your INSTRUM_IF macro's output uniquely identifies the specific if statement then it ispretty straightforward to analyze the overall test output to see ifevery if statement reported its condition both true and false at leastonce. Otherwise, a more involved instrumentation is necessary.
Instrumenting the whilestatements
The
# definewhile(condition) INSTRUM_WHILE(condition)
#endif
<>And of course a mutatis mutandisentry is added to the instrumentation specific header such as
instrum_common.h.
                              int condition,
                              const char *filename,
                              int line,
                              const char *function_name);
__FILE__, __LINE__, __FUNCTION__))
An implementation of the function instrum_while may, however, have apeculiarity. There are two idioms,
The second one is a part of the
You might want to instrument
                    Â
                    Â
                    Â
                    Â
{
  Â
      strcmp(condition_name, “1”) != 0){
         printf(“Loopcond. %s in function %s”
               ” (file %s line %d) is %sn”,
            condition_name, function_name,
            filename, line,(condition)?”true”:”false”);
   }
  Â
}
Of course an implementation like this should be kept in a file whichis not instrumented.
Instrumenting the switchstatements
If the goal of instrumentation is simply to announce the value of thecontrolling expression of a
# define  switch (ctl_stmt)INSTRUM_SWITCH(ctl_stmt)
#endif
ininstrum.h etc .
This would be good enough for establishing a regression base andusing it in regression tests. However, if we want to prove codecoverage, we need information on whether a given
Instrumenting the defaultstatements
To instrument default, let's follow our usual pattern and put ininstrum.h
# definedefault INSTRUM_DEFAULT
#endif
Now, to sensibly define
in any context is functionally equivalent to
and to
Between the two I don't have a preference. (
For falseval we can take
(instrum_default(__FILE__,__LINE__,__FUNCTION__),
!instrum_false)
where
                                   Â
is some function that e.g. announces hitting a default statement,and
extern
is a variable with the value 0 in a different translation unit (
For a unique label we can take a concatenation of the wordinstrum_label and the line number (e.g. a unique label for line 2007will be instrum_label2007 . Thisis a common C fare –
CAT(instrum_label,__LINE__)
where the CAT macro concatenates the two
NOTE. You may choose to run your (instrumented) tests in the hostenvironment when possible. If you use Microsoft Visual C++ to build theexecutable, the construction of the artificial label “CAT(instrum_label_, __LINE__) ” may be broken because of the brokenimplementation of __LINE__. To fix this, you can remove the support for”Edit and Continue” (command-line option /ZI) in the project, or, forversion 7.0 and above, use the non-standard __COUNTER__ instead of__LINE__. Many thanks to Alf P. Steinbach for pointing it out.(Of course, other compilers may have their idiosyncrasies, too.)
Now we are in a position to put the pieces together and to define
     Â
            !instrum_false))
            CAT(instrum_label, __LINE__)
For this instrumentation to compile there cannot be two (or more)
Instrumenting case statements:help from a coding style needed
Our next step is to instrument the
<>With this macro, consider
     Â
     Â
     Â
     Â
The first two occurrences of are not recognized as macro calls(missing parameter list) and are not replaced. The third and the fourthoccurrences are valid macro calls and will be expanded as desired.
Looking at this example from the vantage point of writing the code,we can conclude that for a
Having resigned to instrumenting only
For the implementation of INSTRUM_CASE in
is functionally equivalent to
Following the same pattern as for default, we can do the following:
     Â
__FILE__, __LINE__, __FUNCTION__),!instrum_false))
INSTRUM_CAT(instrum_label, __LINE__)
where instrum_case is an appropriately defined function. The type ofthe second parameter must be large enough to hold any label used inyour application's switch statements. Hopefully, long long will do, ifyour compiler provides it.
It is not uncommon to see several cases in a single line if theyhave common implementation, like
The instrumented code won't compile because of a duplicatedefinition of the artificial label. Again, if you want to use thisinstrumentation, make it a rule to place a case in its own line. If youdon't, the compiler will notify you.Instrumenting the
The syntax of the
in instrum.h ,and in instrum_common.h put
     Â
        Â
         __LINE__,__FUNCTION__),instrum_false)) ;
        Â
Explanation of this macro goes exactly as that for
This instrumentation may be useful for regression testing. To makethe instrumentation more useful for proof of code coverage, we need toresort to the help of the coding policy. We can require thatinstrumentable for statements have macro-ized controlling expressions,i.e., instead of writing
we write
where the non-instrumented ISTRUE macro isdefined as identity macro, i.e. instrum.h has
#ifndefISTRUE
#define ISTRUE(e)
For example, an instrumented version in
instrum_istrue(#e,(e),__FILE__,__LINE__,__FUNCTION__)
with an appropriate definition of the function
This affects the coding style even more intrusively thaninstrumentation of the
Instrumenting the breakstatements and others
There is nothing interesting to learn about a break statement otherthan that it was executed. A definition like this will do:
instrum.h
#ifdef INSTRUM_BREAK
#Â Â Â define break INSTRUM_BREAK
#endif
instrum_common.h
     Â
The purpose of the function instrum_break is merely to announce theexecution of the corresponding
The explanation of this scheme exactly follows that of default, andis based on the observation that
in any context is functionally equivalent to
It should be noted that instrumenting the break (and continue)statements adds nothing to code coverage analysis: each is the lastexecutable statement in a conditional branch (
So instrumenting the condition evaluation (
That said, your coding policy may prohibit using
Still, all of
     Â
      instrum_false)) {
Next in Part 3: Putting it alltogether
To read Part 1, go to “
Ark Khasin, PhD, is with