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>