Java: testing a method with a tiny test runner
Check normal cases, boundaries, and error cases, and report which ones pass.
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
Explanation
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.
Explore the dry run
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 |
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.