According to the Stack Overflow Developer Survey 2024, TestNG remains one of the top 5 Java testing frameworks used by professional developers, particularly in enterprise environments where Selenium WebDriver automation requires advanced test orchestration. Research from JetBrains’ State of Developer Ecosystem 2024 found that 68% of Java enterprise test teams use TestNG for end-to-end testing suites — primarily because of its built-in parallel execution, data providers, and XML-based suite configuration that JUnit required third-party plugins to match. Unlike JUnit which was designed for unit testing, TestNG was architected from the ground up for integration and end-to-end testing: test dependencies, grouping, parameterized suites, and Selenium grid configuration are native features rather than add-ons. Teams migrating 500+ sequential tests to TestNG parallel execution typically see CI pipeline reductions of 60-75% — the difference between 2-hour feedback loops and 30-minute ones.

TL;DR

  • TestNG is a powerful Java testing framework with rich annotation support
  • Key annotations: @Test, @BeforeMethod, @AfterMethod, @DataProvider
  • Configure test suites via testng.xml for grouping, parallelization, parameters
  • Data providers enable data-driven testing with multiple input sets
  • Integrates seamlessly with Selenium WebDriver for browser automation

Best for: Java developers, Selenium automation, enterprise test frameworks

Skip if: Using Kotlin (prefer Kotest) or simple unit tests (JUnit sufficient) Reading time: 15 minutes

Your Selenium tests run sequentially. 500 tests take 2 hours. CI/CD pipeline blocks every PR. Developers ignore test results because feedback comes too late.

TestNG solves test execution problems. Parallel execution, flexible configuration, data-driven testing — all built-in. No additional frameworks needed.

This tutorial covers TestNG from setup to advanced features — everything for building scalable test automation.

What is TestNG?

TestNG (Test Next Generation) is a testing framework for Java inspired by JUnit and NUnit. It goes beyond simple unit testing with features designed for integration and end-to-end testing.

Why TestNG:

  • Rich annotations — @Test, @Before/@After at multiple levels
  • Parallel execution — built-in support for concurrent testing
  • Data providers — elegant data-driven testing
  • Test dependencies — control execution order
  • XML configuration — flexible suite organization
  • HTML reports — built-in test reporting

Setup

Maven

<dependencies>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>7.9.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.2.5</version>
            <configuration>
                <suiteXmlFiles>
                    <suiteXmlFile>testng.xml</suiteXmlFile>
                </suiteXmlFiles>
            </configuration>
        </plugin>
    </plugins>
</build>

Gradle

dependencies {
    testImplementation 'org.testng:testng:7.9.0'
}

test {
    useTestNG()
}

Basic Annotations

@Test

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

public class CalculatorTest {

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

    @Test
    public void testSubtraction() {
        Calculator calc = new Calculator();
        assertEquals(calc.subtract(5, 3), 2);
    }

    @Test(enabled = false)  // Skip this test
    public void testDivision() {
        // Not implemented yet
    }

    @Test(expectedExceptions = ArithmeticException.class)
    public void testDivideByZero() {
        Calculator calc = new Calculator();
        calc.divide(10, 0);
    }
}

@Before and @After

import org.testng.annotations.*;

public class DatabaseTest {

    private Connection connection;

    @BeforeSuite
    public void setupSuite() {
        // Runs once before all tests in suite
        System.out.println("Starting test suite");
    }

    @BeforeClass
    public void setupClass() {
        // Runs once before first test in class
        connection = Database.connect();
    }

    @BeforeMethod
    public void setupMethod() {
        // Runs before each @Test method
        connection.beginTransaction();
    }

    @Test
    public void testInsert() {
        // Test code
    }

    @Test
    public void testUpdate() {
        // Test code
    }

    @AfterMethod
    public void teardownMethod() {
        // Runs after each @Test method
        connection.rollback();
    }

    @AfterClass
    public void teardownClass() {
        // Runs once after last test in class
        connection.close();
    }

    @AfterSuite
    public void teardownSuite() {
        // Runs once after all tests in suite
        System.out.println("Suite completed");
    }
}

Execution Order

@BeforeSuite
  @BeforeClass
    @BeforeMethod
      @Test (method 1)
    @AfterMethod
    @BeforeMethod
      @Test (method 2)
    @AfterMethod
  @AfterClass
@AfterSuite

Data Providers

Basic Data Provider

import org.testng.annotations.*;

public class LoginTest {

    @DataProvider(name = "loginCredentials")
    public Object[][] provideCredentials() {
        return new Object[][] {
            {"user1@example.com", "password1", true},
            {"user2@example.com", "password2", true},
            {"invalid@example.com", "wrongpass", false},
            {"", "password", false},
            {"user@example.com", "", false}
        };
    }

    @Test(dataProvider = "loginCredentials")
    public void testLogin(String email, String password, boolean expectedResult) {
        LoginPage loginPage = new LoginPage(driver);
        boolean result = loginPage.login(email, password);
        assertEquals(result, expectedResult,
            "Login failed for: " + email);
    }
}

External Data Provider

public class TestDataProvider {

    @DataProvider(name = "csvData")
    public static Object[][] readCsvData() throws IOException {
        List<Object[]> data = new ArrayList<>();
        try (BufferedReader reader = new BufferedReader(
                new FileReader("testdata/users.csv"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                String[] values = line.split(",");
                data.add(values);
            }
        }
        return data.toArray(new Object[0][]);
    }
}

// Use in another class
public class UserTest {
    @Test(dataProvider = "csvData", dataProviderClass = TestDataProvider.class)
    public void testUserCreation(String name, String email, String role) {
        // Test with CSV data
    }
}

Test Configuration (testng.xml)

Basic Configuration

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">

<suite name="Regression Suite" verbose="1">

    <test name="Login Tests">
        <classes>
            <class name="com.example.tests.LoginTest"/>
            <class name="com.example.tests.LogoutTest"/>
        </classes>
    </test>

    <test name="Dashboard Tests">
        <classes>
            <class name="com.example.tests.DashboardTest"/>
        </classes>
    </test>

</suite>

Package-Based Selection

<suite name="Full Suite">
    <test name="All Tests">
        <packages>
            <package name="com.example.tests.*"/>
        </packages>
    </test>
</suite>

Parameters

<suite name="Cross Browser Suite">
    <test name="Chrome Tests">
        <parameter name="browser" value="chrome"/>
        <parameter name="baseUrl" value="https://example.com"/>
        <classes>
            <class name="com.example.tests.LoginTest"/>
        </classes>
    </test>

    <test name="Firefox Tests">
        <parameter name="browser" value="firefox"/>
        <parameter name="baseUrl" value="https://example.com"/>
        <classes>
            <class name="com.example.tests.LoginTest"/>
        </classes>
    </test>
</suite>
public class LoginTest {
    private WebDriver driver;

    @Parameters({"browser", "baseUrl"})
    @BeforeMethod
    public void setup(String browser, String baseUrl) {
        if (browser.equals("chrome")) {
            driver = new ChromeDriver();
        } else if (browser.equals("firefox")) {
            driver = new FirefoxDriver();
        }
        driver.get(baseUrl);
    }

    @Test
    public void testLogin() {
        // Test runs with configured browser
    }
}

Parallel Execution

Configuration Options

<!-- Parallel by methods -->
<suite name="Parallel Suite" parallel="methods" thread-count="4">
    <test name="Tests">
        <classes>
            <class name="com.example.tests.LoginTest"/>
        </classes>
    </test>
</suite>

<!-- Parallel by classes -->
<suite name="Parallel Suite" parallel="classes" thread-count="4">
    <test name="Tests">
        <classes>
            <class name="com.example.tests.LoginTest"/>
            <class name="com.example.tests.DashboardTest"/>
        </classes>
    </test>
</suite>

<!-- Parallel by tests -->
<suite name="Parallel Suite" parallel="tests" thread-count="2">
    <test name="Test Group 1">
        <classes>
            <class name="com.example.tests.LoginTest"/>
        </classes>
    </test>
    <test name="Test Group 2">
        <classes>
            <class name="com.example.tests.DashboardTest"/>
        </classes>
    </test>
</suite>

Thread-Safe WebDriver

public class BaseTest {
    protected static ThreadLocal<WebDriver> driver = new ThreadLocal<>();

    @BeforeMethod
    public void setup() {
        WebDriver webDriver = new ChromeDriver();
        driver.set(webDriver);
    }

    @AfterMethod
    public void teardown() {
        if (driver.get() != null) {
            driver.get().quit();
            driver.remove();
        }
    }

    protected WebDriver getDriver() {
        return driver.get();
    }
}

public class LoginTest extends BaseTest {
    @Test
    public void testLogin() {
        getDriver().get("https://example.com");
        // Test is thread-safe
    }
}

Test Groups

Defining Groups

public class UserTest {

    @Test(groups = {"smoke", "regression"})
    public void testUserLogin() {
        // Critical test - runs in both groups
    }

    @Test(groups = {"regression"})
    public void testUserProfile() {
        // Only runs in regression
    }

    @Test(groups = {"regression", "slow"})
    public void testUserReports() {
        // Slow test
    }
}

Running Groups

<suite name="Smoke Suite">
    <test name="Smoke Tests">
        <groups>
            <run>
                <include name="smoke"/>
            </run>
        </groups>
        <packages>
            <package name="com.example.tests.*"/>
        </packages>
    </test>
</suite>

<!-- Exclude slow tests -->
<suite name="Fast Suite">
    <test name="Fast Tests">
        <groups>
            <run>
                <include name="regression"/>
                <exclude name="slow"/>
            </run>
        </groups>
        <packages>
            <package name="com.example.tests.*"/>
        </packages>
    </test>
</suite>

Test Dependencies

public class OrderTest {

    @Test
    public void testLogin() {
        // Login first
    }

    @Test(dependsOnMethods = {"testLogin"})
    public void testAddToCart() {
        // Runs after login succeeds
    }

    @Test(dependsOnMethods = {"testAddToCart"})
    public void testCheckout() {
        // Runs after add to cart succeeds
    }

    @Test(dependsOnMethods = {"testCheckout"})
    public void testPayment() {
        // Final step
    }
}

Soft Dependencies

@Test(dependsOnMethods = {"testLogin"}, alwaysRun = true)
public void testLogout() {
    // Runs even if testLogin fails
}

Selenium Integration

Complete Example

public class SeleniumBaseTest {
    protected WebDriver driver;
    protected WebDriverWait wait;

    @BeforeMethod
    public void setup() {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--headless");
        driver = new ChromeDriver(options);
        driver.manage().window().maximize();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        wait = new WebDriverWait(driver, Duration.ofSeconds(15));
    }

    @AfterMethod
    public void teardown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

public class LoginTest extends SeleniumBaseTest {

    @Test(groups = {"smoke"})
    public void testSuccessfulLogin() {
        driver.get("https://example.com/login");

        driver.findElement(By.id("email")).sendKeys("user@example.com");
        driver.findElement(By.id("password")).sendKeys("password123");
        driver.findElement(By.id("login-btn")).click();

        WebElement welcome = wait.until(
            ExpectedConditions.visibilityOfElementLocated(By.id("welcome"))
        );
        assertTrue(welcome.isDisplayed());
        assertTrue(welcome.getText().contains("Welcome"));
    }

    @DataProvider(name = "invalidLogins")
    public Object[][] invalidLoginData() {
        return new Object[][] {
            {"", "password", "Email is required"},
            {"user@example.com", "", "Password is required"},
            {"invalid", "password", "Invalid email format"}
        };
    }

    @Test(dataProvider = "invalidLogins", groups = {"regression"})
    public void testInvalidLogin(String email, String password, String expectedError) {
        driver.get("https://example.com/login");

        driver.findElement(By.id("email")).sendKeys(email);
        driver.findElement(By.id("password")).sendKeys(password);
        driver.findElement(By.id("login-btn")).click();

        WebElement error = wait.until(
            ExpectedConditions.visibilityOfElementLocated(By.className("error"))
        );
        assertEquals(error.getText(), expectedError);
    }
}

TestNG with AI Assistance

AI tools can help write and optimize TestNG tests.

What AI does well:

  • Generate test methods from requirements
  • Create data providers with edge cases
  • Suggest assertion patterns
  • Convert JUnit tests to TestNG

What still needs humans:

  • Understanding business logic
  • Choosing test granularity
  • Balancing coverage vs maintenance
  • Debugging flaky tests

“TestNG became my default Java test framework after seeing how much easier parallel Selenium execution is compared to JUnit. The testng.xml configuration makes it trivial to split suites across browsers, environments, and thread pools — things that require custom runners in JUnit. For enterprise Selenium projects, I rarely recommend anything else.” — Yuri Kan, Senior QA Lead

FAQ

What is TestNG?

TestNG is a Java testing framework providing annotations, XML suite configuration, parallel execution, and data-driven testing — widely used with Selenium WebDriver.

TestNG (Test Next Generation) is a Java testing framework inspired by JUnit and NUnit. It provides powerful features beyond simple unit testing: annotations for lifecycle management, parallel test execution, data-driven testing via data providers, test dependencies, and flexible XML configuration for organizing test suites. It’s widely used with Selenium for browser automation.

TestNG vs JUnit — which is better?

TestNG for Selenium automation and complex test orchestration; JUnit 5 for unit testing and Spring Boot integration.

TestNG offers more built-in features: parallel execution, test dependencies, data providers, and flexible XML configuration. JUnit 5 has added many similar features, narrowing the gap. TestNG remains popular for Selenium projects due to its XML suite configuration and established ecosystem. For new projects, either works well — choose based on team familiarity.

Is TestNG free?

Yes — TestNG is 100% free and open-source under Apache 2.0 license, available via Maven or Gradle.

Yes, TestNG is completely free and open-source under the Apache 2.0 license. There are no paid versions, enterprise editions, or premium features. All functionality — parallel execution, data providers, reporting — is available at no cost.

Can TestNG run tests in parallel?

Yes — configure parallel="methods" and thread-count in testng.xml; use ThreadLocal for thread-safe WebDriver instances.

Yes, TestNG has excellent built-in parallel execution support. Configure parallelism at method, class, or test level via testng.xml. Set thread count based on your machine capabilities. For Selenium tests, use ThreadLocal to ensure thread-safe WebDriver instances. Parallel execution can dramatically reduce test suite runtime.

Official Resources

See Also