Step 7: Test the rules
Tests turn your rules into checks that run in a second. This example builds a tiny test framework (a test method that reports pass or fail) so you can see what a test runner does, and tests each rule: new accounts are empty, deposits and withdrawals change the balance, an overdraw is refused and leaves the balance unchanged, and non-positive amounts are rejected. One test is deliberately wrong so you can see how a failure is reported.
import java.util.*;
public class Main {
static class Account {
private long cents;
void deposit(long amount) {
if (amount <= 0) throw new IllegalArgumentException("deposit must be positive");
cents += amount;
}
void withdraw(long amount) {
if (amount <= 0) throw new IllegalArgumentException("withdrawal must be positive");
if (amount > cents) throw new IllegalStateException("insufficient funds");
cents -= amount;
}
long balance() { return cents; }
}
// a tiny test framework: run each test, report pass or fail
static int failures = 0;
static void test(String name, Runnable body) {
try {
body.run();
System.out.println("ok " + name);
} catch (AssertionError | RuntimeException e) {
failures++;
System.out.println("FAIL " + name + " - " + e.getMessage());
}
}
static void check(boolean condition, String message) { if (!condition) throw new AssertionError(message); }
static void expectThrows(Class<? extends Exception> type, Runnable body) {
try { body.run(); } catch (Exception e) { if (type.isInstance(e)) return; throw new AssertionError("wrong exception " + e); }
throw new AssertionError("expected " + type.getSimpleName());
}
public static void main(String[] args) {
test("new account is empty", () -> check(new Account().balance() == 0, "balance should be 0"));
test("deposit adds to the balance", () -> { Account a = new Account(); a.deposit(500); a.deposit(250); check(a.balance() == 750, "expected 750"); });
test("withdraw subtracts", () -> { Account a = new Account(); a.deposit(500); a.withdraw(200); check(a.balance() == 300, "expected 300"); });
test("cannot overdraw", () -> { Account a = new Account(); a.deposit(100); expectThrows(IllegalStateException.class, () -> a.withdraw(101)); check(a.balance() == 100, "balance must not change"); });
test("rejects non-positive amounts", () -> { Account a = new Account(); expectThrows(IllegalArgumentException.class, () -> a.deposit(0)); expectThrows(IllegalArgumentException.class, () -> a.withdraw(-5)); });
test("deliberately wrong test", () -> check(new Account().balance() == 1, "balance is 0, not 1"));
System.out.println(failures == 0 ? "all tests passed" : failures + " test(s) failed");
}
}ok new account is empty ok deposit adds to the balance ok withdraw subtracts ok cannot overdraw ok rejects non-positive amounts FAIL deliberately wrong test - balance is 0, not 1 1 test(s) failed
In a real project you would use JUnit, but the idea is identical. The most valuable tests are on the failure paths: the happy path usually works, bugs hide in what happens when something is refused.
Which tests find the most bugs in a bank account class?
Finish the quick check (and any code exercise) to complete this lesson, or mark it done yourself.
Tip: use the left and right arrow keys to move between lessons.