Page 3 of 7
Maintenance also benefits from unit tests. It is often easier to figure out a component's expected use by reading its tests than by reading the object itself. As maintenance developers make changes, they use the unit tests, which give instant feedback. Regression testing doesn't have to take place immediately before release; you can test continually -- every 10 or 15 minutes. You have captured the requirements for your code in the one place guaranteed to live as long as the code -- the code itself.
The JUnit framework, written by Erich Gamma and Kent Beck, enables comprehensive unit testing by making it easy -- even fun
-- to write test code. With JUnit, each test case extends the TestCase class. Any public, no-argument method in which the name starts with "test" is executed one at a time. The test methods call
the component under test and make one or more assertions about the behavior of that component. JUnit reports the precise location of each failed assertion.
As an example, let's think about a simple telephone number object. What behavior should it have? At a minimum, it will have accessors for the components of a U.S. number: area code, exchange, number, and extension. We should also have a formatter that gives a nice string representation. Remember, we write the test first.
The following idiom starts a test case that can run by itself or be incorporated into a larger suite of tests:
import junit.framework.*;
public class TelephoneNumberTests extends TestCase {
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
public static TestSuite suite() {
return new TestSuite(TelephoneNumberTests.class);
}
}
This test will execute. Since there are no test methods, it reports an error. What do we need to add for test methods? The rule of thumb is: If it can possibly fail, write a test for it. There are some exceptions to this rule, however. For example, since simple accessor methods rarely fail, they rarely are tested. Of course, if your accessor does more than just set an instance variable, you should write a test for it. Choosing precisely what to test takes some practice.
Here is a test method for the string formatting:
public void testSimpleStringFormatting() throws Exception {
// Build a complete phone number
TelephoneNumber number = new TelephoneNumber("612", "630", "1063",
"1623");
assertEquals("Bad string", "(612) 630-1063 x1623",
number.formatNumber());
}
The assertEquals() method takes a message string, the expected value, and the actual value. Internally, if expected.equals(actual) returns false, the assertion fails.
This method verifies the basic case, but we can think of many ways for string formatting to fail. What happens if any or all
of the parts are null? toString() must do something reasonable. Let's say the area code and extension are optional, but the exchange and number must be present
or a NullPointerException will be thrown. Notice that we are defining the contract of the TelephoneNumber object well before writing the object itself. We are focused on its expected behavior, not on what is easiest to implement.