Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Debugging and Testing Made Easy (Part 1)

0.00/5 (No votes)
9 Mar 2003 2  
Some macros and tips to take the air out of bugs, and classes to make unit testing painless and simple

Introduction

How often have you found yourself in the situation that you are debugging some code and everything seems logical but it just doesn't work? Quite often as a beginner. Well, I found myself in this situation much less these days because of some great tips I have picked up. I want to share them with you'll and hope they will help you too.

Background

I program in VC++, but quite often, I find myself writing console utilities for various stuff. VC++ and MFC have some great macros (C++ #defines) to help you out in debugging. But if you aren't using MFC in your project, then you are stuck. Well, those macros aren't a lot of magic and are quite easy to reproduce for your non-MFC projects (and non VC++ projects too.)

One of the biggest reasons (according to me, at least) there are bugs in the code, is incorrect assumptions about what a particular piece of code is supposed to (and not supposed to) do. So the way out is to document your assumptions in code, and make your code give you warnings when it is not working as expected, or is being used (or misused) in a wrong way.

Some theory (I hope it's not boring) - Bertrand Meyer introduced the concept of design by contract to do exactly the above (and much more, but we don't want to get in those gory details in this article.) But C++ does not support contracts like the way eiffel does. So what do we do. We try to come up with our ways of implementing contracts.

What the hell is this contract I'm talking about? I don't need no legal mumbo jumbo for programming. You don't. Basically, a contract for a function (or method) indicates what conditions it expects to be satisfied to be able to work correctly, what conditions are imposed upon it by the caller, and finally if there are some global conditions which shouldn't be violated.

The conditions which must be satisfied for a function to work correctly are its preconditions or "REQUIRE"-ments. For example, arguments shouldn't be null is a very common requirement. The conditions imposed by the caller of a particular function must be met. This is to "ENSURE" that result is as expected. For example, if function involves working with files, that the operations were indeed completed, and not aborted. The global conditions are typically called invariants or "ASSERT"-ions.

When we are debugging or in development, we want to be informed when the contracts are broken. Because when the contract is broken, it indicates that the result may not necessarily be correct and something is amiss. One of the easiest ways is to print messages to the console like preconditions not satisfied, assertions failed, or so and so variable has this value while this assertion failed. Well, here are some macros that do exactly that.

#define DBGOUT cout //you could just as easily put 
                    //name of a ofstream here and log everything to a file.

#define REQUIRE(cond) \
if (!(cond))\
{\
    DBGOUT << "\nPrecondition  \t" << #cond << "\tFAILED\t\t" \
           << __FILE__ << "(" << __LINE__ << ")";\
};\

//precondition tracing macro

#define ENSURE(cond)\
if (!(cond)) \
{\
    DBGOUT << "\nPostcondition \t" << #cond << "\tFAILED\t\t" \
           << __FILE__ << "(" << __LINE__ << ")";\
};\

//postcondition tracing macro

#define ASSERT(cond) \
if (!(cond)) \
{\
    DBGOUT << "\nAssertion     \t" << #cond << "\tFAILED\t\t" \
           << __FILE__ << "(" << __LINE__ << ")";\
};\

//invariant tracing macro

#define TRACE(data) \
    DBGOUT << "\nTrace         \t" << #data << " : " << data \
           << "\t\t" << __FILE__ << "(" << __LINE__ << ")";

//dump some variables value.

#define WARN(str) \
    DBGOUT << "\nWarning       \t" << #str << "\t\t" \
           << __FILE__ << "(" << __LINE__ << ")";

//print some warning indicating that some code is being executed which 
//shouldn't really be executed.

Using the Code

Let's take the example of a function to divide two numbers and see where these macros would help us.

int div (int a, int b)
{
  //preconditions
  REQUIRE(b != 0)

  int result = a/b;

  //postconditions
  ENSURE(b*result <= a)

  return a/b;
}

The above example may look trivial, but consider more complicated functions, and with proper use of the above macros, you will at least the most common causes of errors. TRACE and WARN macros will be particularly useful in figuring out why something is not what it should be, and ENSURE, REQUIRE and ASSERT will give you confidence that your functions, if given correct input, will produce correct output.

Slightly more complicated example:

//to parse some string in numbers (thousand) to its equivalent integer(1000)
int Parse(char* str)
{
  //preconditions
  REQUIRE(str != NULL) //null strings cannot be parsed
  int result;

  //some table lookup is done here
  //assert that index is within array bounds
  ASSERT (lookupindex < tablesize)

  //postconditions
  ENSURE(result >= 0) // the caller doesnt expect negative answers
  return result;
}

Misuse

  1. There are some drawbacks and misuse of these macros. The first is, they slow down processing. So, you typically don't want them in the release code, only while developing or debugging, in that case you conditionally define the macros to produce code in debug, but not in release (see the zip file, it is defined that way in the dbgmacros.h).
  2. Secondly, the macros expect to test invariant conditions and don't expect some processing to be done in them. For example, ASSERT(i++!=10) - this code will most possibly fail in your release. Don't do processing in the macros. Only check conditions.

Points of Interest

To dig further, I urge you to dig deeper into the design by contracts philosophy. It has tremendous impact on how object oriented systems are designed and implemented.

I will come up with similar macros to ease up your testing in the next part of this article, probably next week.

History

  • March 10th, 2003: First revision

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here