Complementary Approaches to Testing

While testing is always essential, there are some complementary approaches we can use as part of our overall QA strategy. J2SE 1.4 introduces assertions into the Java language. An assertion is a statement containing a Boolean expression that the programmer believes must be true at that point in the code. An assertion mechanism aborts program execution if a failed assertion is encountered, as this indicates an illegal state. The goal of assertions is to minimize the likelihood of a program containing errors, by identifying erroneous states as soon as possible at run time. See for a description of the Java 1.4 assertion mechanism. The assert keyword is used before assertions, as follows:

 assert booleanConditionalThatMustBeTrue;

The concept of assertions long predates Java: they are, for example, discussed in David Gries' The Science of Programming (Springer, 1981), which suggests trying to prove programs correct, rather than debugging. Unlike test cases, assertions don't necessarily relate to what the code does. Assertions may be based on low-level implementation details, such as the values of private variables, which are not exposed by the object in question. This is one reason that assertions complement, rather than conflict with, the use of unit tests. In my view, assertions are the correct place for checks of how code works, rather than what it does. They are a superior alternative to white-box testing. Assertions should be invisible to users of the code; they have nothing to do with the contracts of the classes involved. Assertions often concern invariants. An invariant is a predicate that is always true. Common examples are class invariants (invariants based on a class's member data) and loop invariants (invariants that are true before and after each iteration of a loop). If an invariant does not hold, the object or method concerned contains a bug or was improperly used. A common way of using invariants is for each method that changes a class's data to assert a class invariant immediately before returning Assuming that we allow access to class data only through the class's methods - an essential part of good design - we can be sure that invariants hold at the beginning of each method's execution. Thus an assertion failure at the end of a method indicates a bug in that method, rather than the object being in an illegal state before the method was called - a problem that might be much harder to track down. Multiple class invariants can be gathered into a private Boolean method, allowing them to be updated as a single unit if required, and invoked like this:

 assert classlnvariants();

A method asserting the invariants, based on instance data, might look like this:

 private boolean classlnvariants() {
 return != null &&
 this.count = 0 &&
 this.helper 1= null;

It's often worth the effort to identify class invariants. Not only does it enable us to write stronger assertions, it helps to develop understanding of the class itself. Gries argues for developing "a program and its proof hand-in-hand, with the proof ideas leading the way!" Correct use of invariants may also pick up bugs missed by unit tests; occasionally, unit tests for an individual method might work correctly, but miss the fact that the method has corrupted the state of the object that defines it. Unlike test cases, assertions are integrated into app code. By introducing the new assert keyword; Java 1.4 ensures that there should be no performance penalty for liberal use of assertions: it's possible to enable or disable assertions at run time, down to individual class level. It has always been possible to use assertions in Java, using code such as this:

 if (orderCount < 0)
 throw new RuntimeException ("Assertion Failed: Illegal order count of i ("
 + i + ") when about to process orders");

Sometimes a static Assertions class is used to provide convenient assertion methods, as does the JUnit testing framework. However, this is relatively verbose, isn't a widely used Java idiom, and has the disadvantage that the assertion condition must always be evaluated. In the simple case above, the performance cost is negligible, but more complex assertion conditions may be slow to compute. A check can be made that assertions are enabled before each assertion is evaluated, but this adds further complexity and is inelegant. Thus the language-level assertion mechanism in Java 1.4 is a big step forward. Note, however, that where assertions concern the usage of a class, the assertion mechanism is inappropriate: the class should simply throw an exception reporting incorrect usage to the erroneous caller. This is a matter of the class's API, not merely internal consistency, so the assertion mechanism, which can be switched off, should not be relied on. In all versions of Java, unchecked exceptions (runtime exceptions that the Java compiler doesn't force us to catch) will have die same effect as assertion failures. This makes them a convenient and correct way of dealing with unrecoverable errors that "shouldn't have happened". We'll talk more about checked and unchecked exceptions in .


J2SE 1.4 introduces major enhancements, of which the new assertion mechanism is one of the most important If using Java 1.4, take advantage of assertions - they're now integrated into the language and offer a performance-neutral way of heading off bugs. Learn to use assertions effectively: this is a skill that takes a little practice to acquire.

Other practices that complement testing include code reviews and XP's more controversial practice of pair programming. Such techniques help to ensure that we write good code, which is less likely to contain bugs and is easier to test and debug. In the next chapter, we'll look at design and coding techniques we can use to ensure that we write good code.