Java course

Test methods and fix a bug

1.6.3 — Test methods and fix a bug

A test calls code with a chosen input and checks its result against an expected value. One successful example is useful, but different inputs can reveal a bug.

A method accepts arguments and gives back a value with return. != means not equal. Each cell includes its own methods and checks; no earlier definitions are needed.

Compare the result with what you expect

The expected result of doubling 3 is 6. Store the actual result, then compare it with that expected value.

A useful check reports both values when they differ. Printing a result alone would make you inspect it manually every time.

Put the comparison in a check method

The supplied check method takes a label, an actual result, and an expected result. If they differ, it throws AssertionError with those details. Otherwise it prints PASS and the label.

throw ends the method call by reporting a failure. This explicit check does not need Java assertion flags or a test library.

In check("three", twice(3), 6), Java calculates twice(3) before passing its result to check.

Read a failed check

This example deliberately compares an actual result of 5 with an expected result of 6. The check fails.

The try/catch here displays that failure so the teaching cell can finish. Catching the error does not make the check pass. In an ordinary test run, an unhandled failed check can stop the run.

Check more than one input

The next method is tested with a positive number, zero, and a negative number. Each case has its own expected result.

Zero and other values at the edge of a rule are boundary cases. They are often where a comparison or special branch goes wrong.

A boundary case can reveal a bug

Requirement: return capacity minus used, but return 0 when used is at least capacity. The next method incorrectly returns 1 in that branch.

The ordinary case passes. The full-capacity case fails, showing the exact input rule that needs repair. >= includes equal values.

Add a check for zero

The next method has another bug: it returns 1 only when value is 0. Its positive-input check passes and misses the bug.

Under the TODO comment, add check("zero", checkedTwice(0), 0);. Run the cell. It should now display FAIL: zero: expected 0, got 1.

This is a useful failing test: it shows a mistake the earlier input did not catch.

Hint

The new check needs the method call checkedTwice(0) as the actual result and 0 as the expected result. Put it after the existing check, inside try.

Practice: repair the zero case and rerun all checks

This independent cell includes all three checks. In checkedTwice, replace the incorrect return 1; with return 0;. Change only that return line.

Run it. Positive, zero, and negative checks should all print PASS. Rerunning the earlier cases after a change checks that the repair has not broken them. These are regression checks.

Hint

Inside the value == 0 branch, return 0. Keep the checks unchanged: their expected values come from the requirement.

Worked solution
static int checkedTwice(int value) {
    if (value == 0) {
        return 0;
    }
    return value * 2;
}
static void check(String label, int actual, int expected) {
    if (actual != expected) {
        throw new AssertionError(label + ": expected " + expected + ", got " + actual);
    }
    System.out.println("PASS: " + label);
}
try {
    check("positive", checkedTwice(3), 6);
    check("zero", checkedTwice(0), 0);
    check("negative", checkedTwice(-2), -4);
} catch (AssertionError error) {
    System.out.println("FAIL: " + error.getMessage());
}

Try a new case

In your completed cell, add one more check inside try:

check("one", checkedTwice(1), 2);

The new check should print PASS: one.

Argument to checkedTwiceExpected result
00
12
-2-4
36

Choose inputs and expected values from the rule, not from whatever the buggy method happens to return.

Enable JavaScript to run the Java notebook.