[Previous] [TOC] [Next]

In-Flight Testing: Using the Assert Statement


One of the most powerful debugging tools available, at least to C programmers, is the Assert macro. Simple in concept, it allows a programmer to write self-checking code by providing an easy method of verifying that a particular condition or assumption is true. Visual Basic programmers had no structured way of doing this until Visual Basic 5. Now we can write statements like this:

  Debug.Assert 2 + 2 = 4

  Debug.Assert bFunctionIsArrayHealthy

  Select Case iUserChoice
      Case 1
          DoSomething1
      Case 2
          DoSomething2
      Case Else
          ' We should never reach here!
          Debug.Assert nUserChoice = 1 Or nUserChoice = 2
  End Select

Debug.Assert operates in the development environment only-conditional compilation automatically drops it from the compiled EXE. It will take any expression that evaluates to either TRUE or FALSE and then drop into break mode at the point the assertion is made if that expression evaluates to FALSE. The idea is to allow you to catch bugs and other problems early by verifying that your assumptions about your program and its environment are true. You can load your program code with debug checks; in fact, you can create code that checks itself while running. Holes in your algorithms, invalid assumptions, creaky data structures, and invalid procedure arguments can all be found in flight and without any human intervention.

The power of assertions is limited only by your imagination. Suppose you were using Visual Basic 6 to control the space shuttle. (We can dream, can't we?) You might have a procedure that shuts down the shuttle's main engine in the event of an emergency, perhaps preparing to jettison the engine entirely. You would want to ensure that the shutdown had worked before the jettison took place, so the procedure for doing this would need to return some sort of status code. To check that the shutdown procedure was working correctly during debugging, you might want to perform a different version of it as well and then verify that both routines left the main engine in the same state. It is fairly common to code any mission-critical system features in this manner. The results of the two different algorithms can be checked against each other, a practice that would fail only in the relatively unlikely situation of both the algorithms having the same bug. The Visual Basic 6 code for such testing might look something like this:

  ' Normal shutdown
  Set nResultOne = ShutdownTypeOne(objEngineCurrentState)

  ' Different shutdown
  Set nResultTwo = ShutdownTypeTwo(objEngineCurrentState)

  ' Check that both shutdowns produced the same result.
  Debug.Assert nResultOne = nResultTwo

When this code was released into production, you would obviously want to remove everything except the call to the normal shutdown routine and let Visual Basic 6's automatic conditional compilation drop the Debug.Assert statement.

You can also run periodic health checks on the major data structures in your programs, looking for uninitialized or null values, holes in arrays, and other nasty gremlins:

  Debug.Assert bIsArrayHealthy CriticalArray

Assertions and Debug.Assert are designed for the development environment only. In the development environment, you are trading program size and speed for debug information. Once your code has reached production, the assumption is that it's been tested well and that assertions are no longer necessary. Assertions are for use during development to help prevent developers from creating bugs. On the other hand, other techniques-such as error handling or defensive programming-attempt to prevent data loss or other undesirable effects as a result of bugs that already exist.

Also, experience shows that a system loaded with assertions can run from 20 to 50 percent slower than one without the assertions, which is obviously not suitable in a production environment. But because the Debug.Assert statements remain in your source code, they will automatically be used again whenever your code is changed and retested in the development environment. In effect, your assertions are immortal-which is as it should be. One of the hardest trails for a maintenance programmer to follow is the one left by your own assumptions about the state of your program. Although we all try to avoid code dependencies and subtle assumptions when we're designing and writing our code, they invariably tend to creep in. Real life demands compromise, and the best-laid code design has to cope with some irregularities and subtleties. Now your assertion statements can act as beacons, showing the people who come after you what you were worried about when you wrote a particular section of code. Doesn't that give you a little frisson?

Another reason why Debug.Assert is an important new tool in your fight against bugs is the inexorable rise of object-oriented programming. A large part of object-oriented programming is what I call "design by contract." This is where you design and implement an object hierarchy in your Visual Basic program, and expose methods and properties of your objects for other developers (or yourself) to use. In effect, you're making a contract with these users of your program. If they invoke your methods and properties correctly, perhaps in a specific order or only under certain conditions, they will receive the services or results that they want. Now you are able to use assertions to ensure that your methods are called in the correct order, perhaps, or that the class initialization method has been invoked before any other method. Whenever you want to confirm that your class object is being used correctly and is in an internally consistent state, you can simply call a method private to that class that can then perform the series of assertions that make up the "health check."

One situation in which to be careful occurs when you're using Debug.Assert to invoke a procedure. You need to bear in mind that any such invocation will never be performed in the compiled version of your program. If you copy the following code into an empty project, you can see clearly what will happen:

  Option Explicit
  Dim mbIsThisDev As Boolean

  Private Sub Form_Load()
  mbIsThisDev = False

  ' If the following line executes, the MsgBox will display
  ' True in answer to its title "Is this development?"
  ' If it doesn't execute, the MsgBox will display false.

  Debug.Assert SetDevFlagToTrue
  MsgBox mbIsThisDev, vbOKOnly, "Is this development?"

  Unload Me
  End Sub

  Private Function SetDevFlagToTrue() As Boolean

  SetDevFlagToTrue = True
  mbIsThisDev = True

  End Function

When you run this code in the Visual Basic environment, the message box will state that it's true that your program is running within the Visual Basic IDE because the SetDevFlagToTrue function will be invoked. If you compile the code into an EXE, however, the message box will show FALSE. In other words, the SetDevFlagToTrue function is not invoked at all. Offhand, I can't think of a more roundabout method of discovering whether you're running as an EXE or in the Visual Basic 6 IDE.

When should you assert?

Once you start using assertions seriously in your code, you need to be aware of some pertinent issues. The first and most important of these is when you should assert. The golden rule is that assertions should not take the place of either defensive programming or data validation. It is important to remember, as I stated earlier, that assertions are there to help prevent developers from creating bugs-an assertion is normally used only to detect an illegal condition that should never happen if your program is working correctly. Defensive programming, on the other hand, attempts to prevent data loss or other undesirable effects as a result of bugs that already exist.

To return to the control software of our space shuttle, consider this code:

  Function ChangeEnginePower(ByVal niPercent As Integer) As Integer
  Dim lNewEnginePower As Long

  Debug.Assert niPercent => -100 And niPercent =< 100
  Debug.Assert mnCurrentPower => 0 And mnCurrentPower =< 100

  lNewEnginePower = CLng(mnCurrentPower) + niPercent

  If lNewEnginePower < 0 Or lNewEnginePower > 100
  Err.Raise vbObjectError + mgInvalidEnginePower
  Else
  mnCurrentPower = lNewEnginePower
  End If

  ChangeEnginePower = mnCurrentPower

  End Sub

Here we want to inform the developer during testing if he or she is attempting to change the engine thrust by an illegal percentage, or if the current engine thrust is illegal. This helps the developer catch bugs during development. However, we also want to program defensively so that if a bug has been created despite our assertion checks during development, it won't cause the engine to explode. The assertion is in addition to some proper argument validation that handles nasty situations such as trying to increase engine thrust beyond 100%. In other words, don't ever let assertions take the place of normal validation.

Defensive programming like the above is dangerous if you don't include the assertion statement. Although using defensive programming to write what might be called nonstop code is important for the prevention of user data loss as a result of program crashes, defensive programming can also have the unfortunate side effect of hiding bugs. Without the assertion statement, a programmer who called the ChangeEnginePower routine with an incorrect argument would not necessarily receive any warning of a problem. Whenever you find yourself programming defensively, think about including an assertion statement.

Explain your assertions

Perhaps the only thing more annoying than finding an assertion statement in another programmer's code and having no idea why it's there is finding a similar assertion statement in your own code. Document your assertions. A simple one- or two-line comment will normally suffice-you don't need to write a dissertation. Some assertions can be the result of quite subtle code dependencies, so in your comment try to clarify why you're asserting something, not just what you're asserting.

Beware of Boolean coercion

The final issue with Debug.Assert is Boolean type coercion. Later in this chapter, we'll look at Visual Basic's automatic type coercion rules and where they can lay nasty traps for you. For now, you can be content with studying the following little enigma:

  Dim nTest As Integer
  nTest = 50
  Debug.Assert nTest
  Debug.Assert Not nTest

You will find that neither of these assertions fire! Strange, but true. The reason has to do with Visual Basic coercing the integer to a Boolean. The first assertion says that nTest = 50, which, because nTest is nonzero, is evaluated to TRUE. The second assertion calculates Not nTest to be -51, which is also nonzero and again evaluated to TRUE.

However, if you compare nTest and Not nTest to the actual value of TRUE (which is -1) as in the following code, only the first assertion fires:

  Debug.Assert nTest = True
  Debug.Assert Not nTest = True

Some final thoughts Debug.Assert is a very powerful tool for bug detection. Used properly, it can catch many bugs automatically, without any human intervention. (See the discussion of an Assertion Sourcerer for a utility that supplements Debug.Assert.) Also see Chapter 1 for further discussion of Debug.Assert.

[Previous] [TOC] [Next]