Data-Driven Testing
Data-driven testing is the practice of separating test logic from test input values, so a single test method executes repeatedly against many different datasets — different username/password combinations, different search terms, different form inputs — instead of writing a near-duplicate test method for every value. In Selenium frameworks this is essential because UI flows like login, registration, or checkout genuinely need to be verified against many boundary and equivalence-class inputs (valid credentials, invalid password, locked account, empty field), and hardcoding a separate test per case would balloon maintenance cost every time a locator or flow step changes.
Cricket analogy: A bowling machine runs a batsman through the same cover-drive technique against dozens of different deliveries (varying pace, line, and length) rather than facing one ball; data-driven testing runs one test method against dozens of different data rows the same way.
TestNG DataProvider
TestNG's @DataProvider annotation is the most common way to implement data-driven testing in Java Selenium frameworks: a method annotated @DataProvider(name = "loginData") returns an Object[][] where each inner array is one row of parameters, and a @Test method declares matching parameters plus dataProvider = "loginData" to have TestNG invoke it once per row automatically, reporting each invocation as a separate result in the report. Setting the parallel attribute on @DataProvider (parallel = true) lets TestNG execute those data-driven invocations concurrently across threads, which matters when a data set has dozens of rows and each row drives a full Selenium browser session.
Cricket analogy: A net session bowling machine is loaded with a sequence of deliveries (yorker, bouncer, googly) and the batsman faces each one in turn with the same stance and technique; @DataProvider loads a sequence of data rows that the same @Test method faces one after another.
// LoginDataProviderTest.java
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.Assert;
public class LoginDataProviderTest {
@DataProvider(name = "loginData", parallel = true)
public Object[][] loginData() {
return new Object[][] {
{ "qa_user", "Secr3t!", true },
{ "qa_user", "wrongpass", false },
{ "locked_usr","Secr3t!", false },
{ "", "Secr3t!", false },
};
}
@Test(dataProvider = "loginData")
public void loginAttempt(String username, String password, boolean expectedSuccess) {
LoginPage loginPage = new LoginPage(driver);
loginPage.enterCredentials(username, password);
loginPage.clickLogin();
Assert.assertEquals(loginPage.isLoggedIn(), expectedSuccess,
"Mismatch for user: " + username);
}
}
// Reading from CSV with a data provider (Apache Commons CSV)
@DataProvider(name = "csvLoginData")
public Object[][] csvLoginData() throws IOException {
List<CSVRecord> records = CSVParser.parse(
new File("src/test/resources/login_data.csv"),
StandardCharsets.UTF_8, CSVFormat.DEFAULT.withFirstRecordAsHeader())
.getRecords();
Object[][] data = new Object[records.size()][2];
for (int i = 0; i < records.size(); i++) {
data[i][0] = records.get(i).get("username");
data[i][1] = records.get(i).get("password");
}
return data;
}
Keep data-driven test data external to the test code (CSV, Excel via Apache POI, JSON via Jackson, or a database) whenever the dataset is large or maintained by non-developers (like a QA analyst updating a spreadsheet of test credentials). Reserve inline Object[][] data providers for small, stable datasets that rarely change, since they require a code change and recompile to update.
JUnit 5 Parameterized Tests
JUnit 5 achieves the same goal through @ParameterizedTest combined with a data source annotation: @ValueSource for a simple list of one type, @CsvSource for inline comma-separated rows, @CsvFileSource to point at an external CSV file, or @MethodSource to reference a static factory method returning a Stream of Arguments — closely mirroring TestNG's @DataProvider but expressed through JUnit 5's more granular, purpose-specific annotations rather than one general-purpose mechanism.
Cricket analogy: A fielding drill uses different specific setups for catching practice (high catches), for slip cordon reflexes, and for run-out drills, each a purpose-built drill rather than one generic fielding exercise; JUnit 5's specific annotations (@CsvSource, @MethodSource) are those purpose-built variants versus TestNG's one general @DataProvider.
- Data-driven testing separates test logic from input data so one test method runs against many datasets without code duplication.
- TestNG implements this via @DataProvider, returning Object[][] rows consumed by @Test methods declaring matching parameters.
- @DataProvider(parallel = true) lets data-driven test invocations run concurrently across threads for large datasets.
- External data sources (CSV via Apache Commons CSV, Excel via Apache POI, JSON via Jackson) are preferred for large or non-developer-maintained datasets.
- JUnit 5 achieves the same pattern via @ParameterizedTest with @ValueSource, @CsvSource, @CsvFileSource, or @MethodSource.
- Each row of test data typically maps to boundary and equivalence-class values (valid, invalid, empty, locked) for thorough coverage.
- Keeping data external avoids recompiling test code every time input values need to change.
Practice what you learned
1. What type does a TestNG @DataProvider method typically return?
2. What is the benefit of setting parallel = true on a TestNG @DataProvider?
3. Which JUnit 5 annotation would you use to pull parameterized test data directly from an external CSV file?
4. Why is it generally recommended to store large data-driven test datasets externally (CSV/Excel/JSON) rather than inline in code?
5. What is a key advantage of data-driven testing for UI flows like login or checkout?
Was this page helpful?
You May Also Like
Selenium with TestNG
Learn how TestNG's annotations, grouping, and reporting turn raw Selenium WebDriver scripts into a structured, parallelizable, CI-ready test suite.
Selenium with JUnit
Learn how JUnit 5's lifecycle annotations, extension model, and tagging integrate with Selenium WebDriver to build maintainable Java test suites.
Page Object Model (POM)
Understand the Page Object Model design pattern for organizing Selenium tests around reusable, maintainable page classes instead of scattered locators.