Labs ICT
โญ Pro Login

Writing Your First Test

Time to write your first real test. By the end of this lesson, you will have a working test class that you can run. Let us start with a simple example and build from there.

The Class We Want to Test

Let us say we have a Calculator class:


package com.example;

public class Calculator {

    public int add(int a, int b) {
        return a + b;
    }

    public int subtract(int a, int b) {
        return a - b;
    }

    public int multiply(int a, int b) {
        return a * b;
    }

    public int divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("Cannot divide by zero");
        }
        return a / b;
    }
}
    

This is a basic calculator with four operations. We want to make sure each method works correctly. Let us write tests for them.

Writing the Test Class

Create a new test file in the test directory:


package com.example;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

    @Test
    void shouldAddTwoNumbers() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3));
    }

    @Test
    void shouldSubtractTwoNumbers() {
        Calculator calc = new Calculator();
        assertEquals(1, calc.subtract(3, 2));
    }

    @Test
    void shouldMultiplyTwoNumbers() {
        Calculator calc = new Calculator();
        assertEquals(6, calc.multiply(2, 3));
    }

    @Test
    void shouldDivideTwoNumbers() {
        Calculator calc = new Calculator();
        assertEquals(2, calc.divide(6, 3));
    }

    @Test
    void shouldThrowExceptionWhenDividingByZero() {
        Calculator calc = new Calculator();
        assertThrows(ArithmeticException.class, () -> calc.divide(10, 0));
    }
}
    

Anatomy of a Test Method

Let us break down what is happening in each test method:


@Test                            <-- Annotation that marks this as a test
void shouldAddTwoNumbers() {     <-- Method name describes the expected behavior
    Calculator calc = new Calculator();  <-- Arrange: set up the test
    assertEquals(5, calc.add(2, 3));     <-- Act & Assert: run and verify
}
    

Every test method follows the AAA pattern:

  • Arrange - Set up the test data and objects
  • Act - Execute the method you want to test
  • Assert - Verify the result is what you expected

Running Your Tests

Run the tests from the command line:


# Maven
mvn test

# Gradle
gradle test
    

Or from your IDE by right-clicking the test class and selecting "Run."

If all tests pass, you will see something like:


Tests run: 5, Failures: 0, Errors: 0, Skipped: 0

BUILD SUCCESS
    

If a test fails, JUnit tells you exactly which test failed and why. For example:


org.opentest4j.AssertionFailedError:
    expected: <5> but was: <6>
    at CalculatorTest.shouldAddTwoNumbers(CalculatorTest.java:10)
    

Test Method Naming

Notice how the test methods are named. Good test names describe the scenario and the expected behavior. Here are some naming conventions people use:


// Style 1: shouldXxx
void shouldReturnSumWhenGivenTwoNumbers() { }

// Style 2: descriptive
void add_withPositiveNumbers_returnsSum() { }

// Style 3: testXxx
void testAddition() { }

// Style 4: givenXxxWhenYyyThenZzz
void givenTwoNumbers_whenAdded_thenReturnsSum() { }
    

There is no single "right" way. Pick a style and be consistent. The key is that when a test fails, the name should tell you what went wrong.

Your Turn

Try this exercise. Create a StringHelper class with these methods:


public class StringHelper {

    public boolean isPalindrome(String str) {
        String reversed = new StringBuilder(str).reverse().toString();
        return str.equalsIgnoreCase(reversed);
    }

    public int countVowels(String str) {
        int count = 0;
        for (char c : str.toLowerCase().toCharArray()) {
            if ("aeiou".indexOf(c) != -1) {
                count++;
            }
        }
        return count;
    }

    public String reverse(String str) {
        return new StringBuilder(str).reverse().toString();
    }
}
    

Write tests for each method. Think about edge cases: what happens with empty strings? Null values? Single characters? This is how you build a solid test suite.

๐Ÿงช Quick Quiz

Which of these is the correct annotation to mark a method as a test in JUnit 5?