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
FAQ
What is TestNG?
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 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 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, 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
- Selenium Tutorial - WebDriver fundamentals
- JUnit vs TestNG - Framework comparison
- REST Assured Tutorial - Java API testing
- CI/CD Testing Guide - Pipeline integration
