Java: testing a method with a tiny test runner
Check normal cases, boundaries, and error cases, and report which ones pass.
On this page
Java methods and exceptions.
- Check normal cases, boundaries, and error cases, and report which ones pass.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
A unit test calls one piece of code with known inputs and compares the result with the expected value. Good tests cover a typical value, the boundaries, and the error cases. Real Java projects use a framework such as JUnit, but the idea is the same: arrange, act, assert.
This program tests clamp, which limits a value to a range. It checks a value inside the range, below it, above it, and a bad call that must throw. Each check prints PASS or FAIL with the expected and actual values, so a failure explains itself.
Code example
class Main {
static int clamp(int value, int low, int high) {
if (low > high) {
throw new IllegalArgumentException("low > high");
}
return Math.max(low, Math.min(high, value));
}
static int passed = 0;
static int failed = 0;
static void check(String name, int expected, int actual) {
if (expected == actual) {
passed++;
System.out.println("PASS " + name);
} else {
failed++;
System.out.println("FAIL " + name + ": expected " + expected + " but was " + actual);
}
}
public static void main(String[] args) {
check("inside range", 5, clamp(5, 0, 10));
check("below range", 0, clamp(-3, 0, 10));
check("above range", 10, clamp(99, 0, 10));
try {
clamp(1, 5, 2);
failed++;
System.out.println("FAIL bad bounds: no exception");
} catch (IllegalArgumentException e) {
passed++;
System.out.println("PASS bad bounds throws: " + e.getMessage());
}
System.out.println(passed + " passed, " + failed + " failed");
}
}PASS inside range PASS below range PASS above range PASS bad bounds throws: low > high 4 passed, 0 failed
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
class Main { static int clamp(int value, int low, int high) { if (low > high) { throw new IllegalArgumentException("low > high"); } return Math.max(low, Math.min(high, value)); } static int passed = 0; static int failed = 0; static void check(String name, int expected, int actual) { if (expected == actual) { passed++; System.out.println("PASS " + name); } else { failed++; System.out.println("FAIL " + name + ": expected " + expected + " but was " + actual); } } public static void main(String[] args) { check("inside range", 5, clamp(5, 0, 10)); check("below range", 0, clamp(-3, 0, 10)); check("above range", 10, clamp(99, 0, 10)); try { clamp(1, 5, 2); failed++; System.out.println("FAIL bad bounds: no exception"); } catch (IllegalArgumentException e) { passed++; System.out.println("PASS bad bounds throws: " + e.getMessage()); } System.out.println(passed + " passed, " + failed + " failed"); }}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| A typical value is returned unchanged. | expected 5, actual 5 | PASS inside range | |
| A value below the minimum is raised to it. | expected 0, actual 0 | PASS inside range PASS below range | |
| A value above the maximum is lowered to it. | expected 10, actual 10 | PASS inside range PASS below range PASS above range | |
| A final summary counts the results, including the expected exception. | passed = 4, failed = 0 | PASS inside range PASS below range PASS above range PASS bad bounds throws: low > high 4 passed, 0 failed |
Behind the scenes
How Java processes this code
The Java compiler produces bytecode. The JVM loads the class and invokes its main method.
Cost and common mistakes
Each check is O(1); the runner is linear in the number of tests.
- A test that never fails proves little. Make sure each test would fail if the code were wrong.
- Tests should be independent of each other and of the order they run in.
- Catching the exception in the test is part of the check: if no exception is thrown, the test must fail.
Try it yourself
Add a boundary test for clamp(10, 0, 10).
Reveal answer and reasoning
check("at upper bound", 10, clamp(10, 0, 10)); the value is already equal to the maximum, so it is returned unchanged.