Eroxl's Notes
Testing a Data Abstraction

Testing ensures that a method behaves as expected under the conditions specified in its REQUIRES, MODIFIES, and EFFECTS clauses.

1. Follow the REQUIRES Clause

  • If a method has a REQUIRES clause, only test cases that satisfy that requirement.
  • Testing cases outside of the specified conditions is not meaningful, as the method’s behavior is undefined in those cases.

Example

// REQUIRES: dx > 0
// MODIFIES: this
// EFFECTS: adds dx to x coordinate
public void move(int dx) { ... }

Since dx > 0 is required, test cases should only include positive values of dx. Values of dx <= 0 should not be tested.

2. Test Boundary and Typical Cases

  • Boundary cases: Test at the edges of valid input ranges (e.g., minimum or maximum allowed values).
  • Typical cases: Test values within the expected range to confirm normal behavior.

Example

If a method works for 0 x 100, test:

  • [x] x = 0 (minimum boundary)
  • [x] x = 100 (maximum boundary)
  • [x] x = 50 (midpoint, typical case)

3. Verify All Aspects of the EFFECTS Clause

  • Check if the Method produces the expected result as described in its EFFECTS clause.
  • If the method has side effects (e.g., modifying an object), ensure those changes are correctly applied.

4. Test Behavior with Multiple Calls

  • If a method modifies an object, test it by calling it multiple times to ensure consistent behavior.

Example

public class BankAccount {
    private double balance;
    private String owner;

    // REQUIRES: initialBalance >= 0
    // EFFECTS: Initializes a bank account with the given owner name
    //          and starting balance
    public BankAccount(String owner, double initialBalance) {
        this.owner = owner;
        this.balance = initialBalance;
    }

    // REQUIRES: amount > 0 && amount <= balance
    // MODIFIES: this
    // EFFECTS: Deducts the given amount from the balance
    public void withdraw(double amount) {
        this.balance -= amount;
    }

	// EFFECTS: Returns the current balance of the account
    public double getBalance() {
        return this.balance;
    }
}
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class BankAccountTest {
    private BankAccount acc;

    @BeforeEach
    void setUp() {
        acc = new BankAccount("Alice", 100);
    }

    @Test
    void testWithdraw() {
        acc.withdraw(30);
        assertEquals(70, acc.getBalance());

        acc.withdraw(20);
        assertEquals(50, acc.getBalance());

        acc.withdraw(50);
        assertEquals(0, acc.getBalance());
    }
}

The test ensures that the withdraw Method consistently reduces the balance as expected.