Back to Chapters

Chapter 13: Practical Examples

In this chapter, we'll explore comprehensive practical examples of TestNG in action, focusing on real-world testing scenarios for different types of applications.

Testing a Calculator Application

Let's start with a complete example of testing a calculator application.

Calculator Implementation

First, let's create a calculator implementation with various operations:

package com.example.calculator;

/**
 * A calculator class that provides basic and advanced mathematical operations.
 */
public class Calculator {

    /**
     * Adds two numbers.
     *
     * @param a first number
     * @param b second number
     * @return the sum of a and b
     */
    public int add(int a, int b) {
        return a + b;
    }

    /**
     * Subtracts the second number from the first.
     *
     * @param a first number
     * @param b second number
     * @return the difference between a and b
     */
    public int subtract(int a, int b) {
        return a - b;
    }

    /**
     * Multiplies two numbers.
     *
     * @param a first number
     * @param b second number
     * @return the product of a and b
     */
    public int multiply(int a, int b) {
        return a * b;
    }

    /**
     * Divides the first number by the second.
     *
     * @param a first number
     * @param b second number
     * @return the quotient of a divided by b
     * @throws ArithmeticException if b is zero
     */
    public double divide(double a, double b) {
        if (b == 0) {
            throw new ArithmeticException("Division by zero");
        }
        return a / b;
    }

    /**
     * Calculates the power of a number.
     *
     * @param base the base number
     * @param exponent the exponent
     * @return the base raised to the power of the exponent
     * @throws IllegalArgumentException if exponent is negative
     */
    public double power(double base, int exponent) {
        if (exponent < 0) {
            throw new IllegalArgumentException("Exponent must be non-negative");
        }

        double result = 1;
        for (int i = 0; i < exponent; i++) {
            result *= base;
        }
        return result;
    }

    /**
     * Calculates the square root of a number.
     *
     * @param a the number
     * @return the square root of a
     * @throws IllegalArgumentException if a is negative
     */
    public double sqrt(double a) {
        if (a < 0) {
            throw new IllegalArgumentException("Cannot calculate square root of negative number");
        }
        return Math.sqrt(a);
    }

    /**
     * Calculates the factorial of a number.
     *
     * @param n the number
     * @return the factorial of n
     * @throws IllegalArgumentException if n is negative
     */
    public long factorial(int n) {
        if (n < 0) {
            throw new IllegalArgumentException("Cannot calculate factorial of negative number");
        }
        if (n == 0 || n == 1) {
            return 1;
        }

        long result = 1;
        for (int i = 2; i <= n; i++) {
            result *= i;
        }
        return result;
    }

    /**
     * Checks if a number is prime.
     *
     * @param n the number to check
     * @return true if n is prime, false otherwise
     * @throws IllegalArgumentException if n is less than 2
     */
    public boolean isPrime(int n) {
        if (n < 2) {
            throw new IllegalArgumentException("Numbers less than 2 are not prime");
        }

        for (int i = 2; i <= Math.sqrt(n); i++) {
            if (n % i == 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * Calculates the greatest common divisor of two numbers.
     *
     * @param a first number
     * @param b second number
     * @return the greatest common divisor of a and b
     */
    public int gcd(int a, int b) {
        a = Math.abs(a);
        b = Math.abs(b);

        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }

        return a;
    }
}

Calculator Test Suite

Now, let's create a comprehensive test suite for the calculator:

package com.example.calculator;

import org.testng.Assert;
import org.testng.annotations.*;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.ArrayList;
import java.util.List;

/**
 * Comprehensive test suite for the Calculator class.
 */
public class CalculatorTest {

    private Calculator calculator;

    @BeforeClass
    public void setUpClass() {
        System.out.println("Setting up Calculator test suite");
    }

    @BeforeMethod
    public void setUp() {
        calculator = new Calculator();
        System.out.println("Creating new Calculator instance for test");
    }

    @Test(groups = {"basic", "addition"})
    public void testAddition() {
        System.out.println("Testing addition");
        Assert.assertEquals(calculator.add(2, 3), 5, "2 + 3 should equal 5");
        Assert.assertEquals(calculator.add(-2, -3), -5, "(-2) + (-3) should equal -5");
        Assert.assertEquals(calculator.add(0, 0), 0, "0 + 0 should equal 0");
    }

    @Test(groups = {"basic", "subtraction"})
    public void testSubtraction() {
        System.out.println("Testing subtraction");
        Assert.assertEquals(calculator.subtract(5, 3), 2, "5 - 3 should equal 2");
        Assert.assertEquals(calculator.subtract(-5, -3), -2, "(-5) - (-3) should equal -2");
        Assert.assertEquals(calculator.subtract(0, 0), 0, "0 - 0 should equal 0");
    }

    @Test(groups = {"basic", "multiplication"})
    public void testMultiplication() {
        System.out.println("Testing multiplication");
        Assert.assertEquals(calculator.multiply(2, 3), 6, "2 * 3 should equal 6");
        Assert.assertEquals(calculator.multiply(-2, -3), 6, "(-2) * (-3) should equal 6");
        Assert.assertEquals(calculator.multiply(0, 5), 0, "0 * 5 should equal 0");
    }

    @Test(groups = {"basic", "division"})
    public void testDivision() {
        System.out.println("Testing division");
        Assert.assertEquals(calculator.divide(6, 3), 2.0, 0.0001, "6 / 3 should equal 2.0");
        Assert.assertEquals(calculator.divide(-6, -3), 2.0, 0.0001, "(-6) / (-3) should equal 2.0");
        Assert.assertEquals(calculator.divide(0, 5), 0.0, 0.0001, "0 / 5 should equal 0.0");
    }

    @Test(groups = {"basic", "division"}, expectedExceptions = ArithmeticException.class)
    public void testDivisionByZero() {
        System.out.println("Testing division by zero");
        calculator.divide(5, 0);
    }

    @Test(groups = {"advanced", "power"})
    public void testPower() {
        System.out.println("Testing power function");
        Assert.assertEquals(calculator.power(2, 3), 8.0, 0.0001, "2^3 should equal 8.0");
        Assert.assertEquals(calculator.power(5, 0), 1.0, 0.0001, "5^0 should equal 1.0");
        Assert.assertEquals(calculator.power(0, 5), 0.0, 0.0001, "0^5 should equal 0.0");
    }

    @Test(groups = {"advanced", "power"}, expectedExceptions = IllegalArgumentException.class)
    public void testPowerWithNegativeExponent() {
        System.out.println("Testing power with negative exponent");
        calculator.power(2, -3);
    }

    @Test(groups = {"advanced", "sqrt"})
    public void testSquareRoot() {
        System.out.println("Testing square root function");
        Assert.assertEquals(calculator.sqrt(4), 2.0, 0.0001, "sqrt(4) should equal 2.0");
        Assert.assertEquals(calculator.sqrt(0), 0.0, 0.0001, "sqrt(0) should equal 0.0");
        Assert.assertEquals(calculator.sqrt(2), 1.4142, 0.0001, "sqrt(2) should approximately equal 1.4142");
    }

    @Test(groups = {"advanced", "sqrt"}, expectedExceptions = IllegalArgumentException.class)
    public void testSquareRootOfNegativeNumber() {
        System.out.println("Testing square root of negative number");
        calculator.sqrt(-4);
    }

    @Test(groups = {"advanced", "factorial"})
    public void testFactorial() {
        System.out.println("Testing factorial function");
        Assert.assertEquals(calculator.factorial(0), 1, "0! should equal 1");
        Assert.assertEquals(calculator.factorial(1), 1, "1! should equal 1");
        Assert.assertEquals(calculator.factorial(5), 120, "5! should equal 120");
    }

    @Test(groups = {"advanced", "factorial"}, expectedExceptions = IllegalArgumentException.class)
    public void testFactorialOfNegativeNumber() {
        System.out.println("Testing factorial of negative number");
        calculator.factorial(-1);
    }

    @Test(groups = {"advanced", "prime"})
    public void testIsPrime() {
        System.out.println("Testing isPrime function");
        Assert.assertTrue(calculator.isPrime(2), "2 should be prime");
        Assert.assertTrue(calculator.isPrime(3), "3 should be prime");
        Assert.assertTrue(calculator.isPrime(5), "5 should be prime");
        Assert.assertTrue(calculator.isPrime(7), "7 should be prime");
        Assert.assertTrue(calculator.isPrime(11), "11 should be prime");
        Assert.assertTrue(calculator.isPrime(13), "13 should be prime");

        Assert.assertFalse(calculator.isPrime(4), "4 should not be prime");
        Assert.assertFalse(calculator.isPrime(6), "6 should not be prime");
        Assert.assertFalse(calculator.isPrime(8), "8 should not be prime");
        Assert.assertFalse(calculator.isPrime(9), "9 should not be prime");
        Assert.assertFalse(calculator.isPrime(10), "10 should not be prime");
        Assert.assertFalse(calculator.isPrime(12), "12 should not be prime");
    }

    @Test(groups = {"advanced", "prime"}, expectedExceptions = IllegalArgumentException.class)
    public void testIsPrimeWithInvalidInput() {
        System.out.println("Testing isPrime with invalid input");
        calculator.isPrime(1);
    }

    @Test(groups = {"advanced", "gcd"})
    public void testGcd() {
        System.out.println("Testing GCD function");
        Assert.assertEquals(calculator.gcd(12, 8), 4, "GCD of 12 and 8 should be 4");
        Assert.assertEquals(calculator.gcd(54, 24), 6, "GCD of 54 and 24 should be 6");
        Assert.assertEquals(calculator.gcd(48, 18), 6, "GCD of 48 and 18 should be 6");
        Assert.assertEquals(calculator.gcd(7, 13), 1, "GCD of 7 and 13 should be 1");
        Assert.assertEquals(calculator.gcd(0, 5), 5, "GCD of 0 and 5 should be 5");
        Assert.assertEquals(calculator.gcd(5, 0), 5, "GCD of 5 and 0 should be 5");
    }

    // Data provider for parameterized tests
    @DataProvider(name = "additionData")
    public Object[][] createAdditionData() {
        return new Object[][] {
            {5, 3, 8},
            {-5, -3, -8},
            {0, 0, 0},
            {Integer.MAX_VALUE, 1, Integer.MIN_VALUE} // Overflow case
        };
    }

    @Test(dataProvider = "additionData", groups = {"basic", "addition"})
    public void testAdditionWithDataProvider(int a, int b, int expected) {
        System.out.println("Testing addition with data provider: " + a + " + " + b);
        Assert.assertEquals(calculator.add(a, b), expected, a + " + " + b + " should equal " + expected);
    }

    // Using Java 21 records for test data
    record OperationData(String operation, double a, double b, double expected) {}

    @DataProvider(name = "operationData")
    public Object[][] createOperationData() {
        return new Object[][] {
            {new OperationData("add", 5, 3, 8)},
            {new OperationData("subtract", 10, 4, 6)},
            {new OperationData("multiply", 3, 7, 21)},
            {new OperationData("divide", 10, 2, 5)}
        };
    }

    @Test(dataProvider = "operationData", groups = "basic")
    public void testOperationsWithRecords(OperationData data) {
        System.out.println("Testing " + data.operation() + " with records: " +
                          data.a() + ", " + data.b());

        double result = switch (data.operation()) {
            case "add" -> calculator.add((int)data.a(), (int)data.b());
            case "subtract" -> calculator.subtract((int)data.a(), (int)data.b());
            case "multiply" -> calculator.multiply((int)data.a(), (int)data.b());
            case "divide" -> calculator.divide(data.a(), data.b());
            default -> throw new IllegalArgumentException("Unknown operation: " + data.operation());
        };

        Assert.assertEquals(result, data.expected(), 0.0001,
                           data.operation() + " operation failed");
    }

    // Using Java 21 virtual threads for concurrent testing
    @Test(groups = "concurrent")
    public void testConcurrentOperations() throws Exception {
        System.out.println("Testing concurrent operations with virtual threads");

        // Define operations
        List<OperationData> operations = List.of(
            new OperationData("add", 5, 3, 8),
            new OperationData("subtract", 10, 4, 6),
            new OperationData("multiply", 3, 7, 21),
            new OperationData("divide", 10, 2, 5)
        );

        List<Future<Double>> results = new ArrayList<>();

        // Using virtual threads
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (OperationData op : operations) {
                results.add(executor.submit(() -> {
                    // Simulate some work
                    Thread.sleep(100);

                    return switch (op.operation()) {
                        case "add" -> (double)calculator.add((int)op.a(), (int)op.b());
                        case "subtract" -> (double)calculator.subtract((int)op.a(), (int)op.b());
                        case "multiply" -> (double)calculator.multiply((int)op.a(), (int)op.b());
                        case "divide" -> calculator.divide(op.a(), op.b());
                        default -> throw new IllegalArgumentException("Unknown operation: " + op.operation());
                    };
                }));
            }
        }

        // Verify results
        for (int i = 0; i < operations.size(); i++) {
            OperationData op = operations.get(i);
            double actualResult = results.get(i).get();

            Assert.assertEquals(actualResult, op.expected(), 0.0001,
                               op.operation() + " result should match");
        }
    }

    @AfterMethod
    public void tearDown() {
        System.out.println("Test completed");
        calculator = null;
    }

    @AfterClass
    public void tearDownClass() {
        System.out.println("Calculator test suite completed");
    }
}

Calculator Test XML Configuration

Let's create a TestNG XML configuration file to run the calculator tests:

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="CalculatorTestSuite">
    <test name="BasicOperationsTest">
        <groups>
            <run>
                <include name="basic"/>
            </run>
        </groups>
        <classes>
            <class name="com.example.calculator.CalculatorTest"/>
        </classes>
    </test>

    <test name="AdvancedOperationsTest">
        <groups>
            <run>
                <include name="advanced"/>
            </run>
        </groups>
        <classes>
            <class name="com.example.calculator.CalculatorTest"/>
        </classes>
    </test>

    <test name="ConcurrentOperationsTest">
        <groups>
            <run>
                <include name="concurrent"/>
            </run>
        </groups>
        <classes>
            <class name="com.example.calculator.CalculatorTest"/>
        </classes>
    </test>
</suite>