Java · Course navigation

Java: testing a method with a tiny test runner

Check normal cases, boundaries, and error cases, and report which ones pass.

On this page
Before you start

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

Java
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");
  }
}
Output
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
Guided walkthroughReady
1class Main {
2 static int clamp(int value, int low, int high) {
3 if (low > high) {
4 throw new IllegalArgumentException("low > high");
5 }
6 return Math.max(low, Math.min(high, value));
7 }
8
9 static int passed = 0;
10 static int failed = 0;
11
12 static void check(String name, int expected, int actual) {
13 if (expected == actual) {
14 passed++;
15 System.out.println("PASS " + name);
16 } else {
17 failed++;
18 System.out.println("FAIL " + name + ": expected " + expected + " but was " + actual);
19 }
20 }
21
22 public static void main(String[] args) {
23 check("inside range", 5, clamp(5, 0, 10));
24 check("below range", 0, clamp(-3, 0, 10));
25 check("above range", 10, clamp(99, 0, 10));
26 try {
27 clamp(1, 5, 2);
28 failed++;
29 System.out.println("FAIL bad bounds: no exception");
30 } catch (IllegalArgumentException e) {
31 passed++;
32 System.out.println("PASS bad bounds throws: " + e.getMessage());
33 }
34 System.out.println(passed + " passed, " + failed + " failed");
35 }
36}

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
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput 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.

Further reading