Test Frameworks
TestNG
TestNG as the representative test-runner: annotations & lifecycle, hard vs soft assertions, data-driven tests, groups / priority / dependencies, parallel execution via testng.xml, and listeners & retries — with side notes mapping each idea to JUnit 5, NUnit, and pytest. Short explanations, short examples.
01What TestNG Is — and Why It Represents the Family
TestNG is a Java testing framework built for more than unit tests — its name (“Next Generation”) signals the extras: flexible configuration, data-driven tests, grouping, dependencies, and built-in parallelism. It’s the default runner in most Java + Selenium stacks.
Almost every test runner shares the same skeleton: lifecycle hooks, assertions, data-driven inputs, parallelism, and reporting. Learn TestNG and you can read JUnit 5, NUnit, and pytest at a glance.
- JUnit 5 (Java) — the closest cousin; same ideas, different annotation names.
- NUnit (C#) — your Playwright stack’s runner; near-identical concepts with C# attribute syntax.
- pytest (Python) — same capabilities via fixtures and decorators instead of annotations.
testng.xml suite file and @DataProvider give finer control over grouping and parallelism than vanilla JUnit — a fair point to raise when asked “why TestNG over JUnit?”.02Annotations & Lifecycle
TestNG runs methods in a fixed order driven by annotations, scoped from the whole suite down to a single method.
| Annotation | Runs |
|---|---|
| @BeforeSuite / @AfterSuite | once around the whole suite |
| @BeforeTest / @AfterTest | around a <test> tag in testng.xml |
| @BeforeClass / @AfterClass | once per class |
| @BeforeMethod / @AfterMethod | before/after every @Test method |
| @Test | the actual test |
public class LoginTest {
@BeforeMethod public void setUp() { driver = new ChromeDriver(); }
@Test public void canLogin() { /* ... */ }
@AfterMethod public void tearDown() { driver.quit(); }
}@BeforeEach/@BeforeAll, NUnit [SetUp]/[OneTimeSetUp], pytest fixtures with scope="function"/"class"/"session". Same lifecycle, different spelling.03Hard vs Soft Assertions
- Hard assertion (
Assert.assertEquals) — fails fast: the test stops at the first failed check. - Soft assertion (
SoftAssert) — collects all failures and reports them together when you callassertAll(); nothing fails until then.
Soft asserts shine when you want to validate several independent things in one test (e.g. five fields on a page) and see all the broken ones in one run.
SoftAssert soft = new SoftAssert();
soft.assertEquals(user.getName(), "Ada");
soft.assertEquals(user.getRole(), "admin"); // still runs even if name failed
soft.assertAll(); // reports every failure nowassertAll(...), NUnit Assert.Multiple, AssertJ/Playwright’s soft assertions. The hard-vs-soft distinction is a common interview question.04Data-Driven Tests
Two ways to feed inputs into a test:
- @DataProvider — a method returning
Object[][]; TestNG runs the test once per row. Best for in-code or computed datasets. - @Parameters — inject simple values from
testng.xml(e.g. a browser name or base URL).
@DataProvider(name = "logins")
public Object[][] logins() {
return new Object[][] {
{ "ada@test.dev", true },
{ "bad@test.dev", false },
};
}
@Test(dataProvider = "logins")
public void login(String email, boolean expected) {
assertEquals(authService.login(email), expected);
}@ParameterizedTest + @MethodSource/@CsvSource, NUnit [TestCase]/[TestCaseSource], pytest @pytest.mark.parametrize. TestNG’s @DataProvider can also be parallel = true.05Groups, Priority & Dependencies
- Groups — tag tests (
@Test(groups = "smoke")) and run a subset fromtestng.xmlor the CLI. Like JUnit 5@Tag, NUnit[Category], pytest markers. - Priority —
@Test(priority = 1)orders tests (default 0); lower runs first. - Dependencies —
dependsOnMethods/dependsOnGroupsskip a test if its prerequisite failed, rather than reporting a misleading second failure.
@Test(groups = "smoke")
public void createUser() { /* ... */ }
@Test(dependsOnMethods = "createUser") // skipped if createUser fails
public void deleteUser() { /* ... */ }dependsOnMethods couples tests and hurts isolation/parallelism. Prefer independent tests; use dependencies only for genuine setup ordering. JUnit deliberately limits ordering for this reason.06Parallel Execution & testng.xml
The testng.xml suite file is TestNG’s control center — it selects classes/groups, injects parameters, and configures parallelism without touching code.
<suite name="regression" parallel="methods" thread-count="4">
<test name="chrome">
<parameter name="browser" value="chrome"/>
<classes>
<class name="tests.LoginTest"/>
</classes>
</test>
</suite>parallel="methods|tests|classes|instances"withthread-countsets the concurrency model.- Parallel tests must be thread-safe — share no mutable state; in Selenium use a
ThreadLocal<WebDriver>so each thread gets its own driver.
junit-platform.properties), NUnit [Parallelizable], pytest-xdist (-n auto). Built-in suite-level parallelism is one of TestNG’s headline features.07Listeners, Retries & Reporting
- Listeners — hook into the test lifecycle (
ITestListener,IRetryAnalyzer) to add logging, screenshots on failure, or custom reporting. - Retry — implement
IRetryAnalyzerto re-run a failed test N times (use sparingly — masks real flakiness). - Reporting — TestNG emits HTML/XML reports; teams usually layer Allure or ExtentReports on top for richer output.
public class ScreenshotOnFail implements ITestListener {
@Override public void onTestFailure(ITestResult result) {
captureScreenshot(result.getName()); // attach to report
}
}
// register: @Listeners(ScreenshotOnFail.class) or in testng.xmlTestWatcher), NUnit ITestAction, pytest hooks (conftest.py) + plugins. Same extension-point idea everywhere.08TestNG vs JUnit 5 vs NUnit vs pytest
The cheat sheet — same concept, four dialects:
| Concept | TestNG | JUnit 5 | NUnit | pytest |
|---|---|---|---|---|
| test | @Test | @Test | [Test] | def test_* |
| before each | @BeforeMethod | @BeforeEach | [SetUp] | fixture (function) |
| before all | @BeforeClass | @BeforeAll | [OneTimeSetUp] | fixture (class/module) |
| data-driven | @DataProvider | @ParameterizedTest | [TestCase] | @parametrize |
| tag / group | groups | @Tag | [Category] | markers |
| skip | @Test(enabled=false) | @Disabled | [Ignore] | @skip |
| parallel | testng.xml | properties file | [Parallelizable] | pytest-xdist |
09Rapid-Fire Q&A
Reveal each answer to self-check, then test yourself with the quiz.
TestNG vs JUnit, briefly?
Same core ideas; TestNG adds built-in suite configuration via testng.xml, flexible @DataProvider, groups, and dependsOnMethods — handy for integration/E2E suites.
Order of TestNG annotations?
Suite → Test → Class → Method around the @Test: @BeforeSuite, @BeforeTest, @BeforeClass, @BeforeMethod, @Test, then the matching @After… in reverse.
Hard vs soft assertion?
Hard stops at the first failure; soft (SoftAssert) collects all failures and reports them together at assertAll().
How do you run data-driven tests?
@DataProvider returns Object[][] and the @Test runs once per row; or @Parameters injects values from testng.xml.
How do you run a subset like smoke tests?
Tag with @Test(groups="smoke") and select that group in testng.xml or on the CLI.
What does dependsOnMethods do?
Runs a test only after its prerequisite passes; if the prerequisite fails, the dependent test is skipped, not failed.
How does TestNG do parallelism?
Set parallel (methods/tests/classes) and thread-count in testng.xml; tests must be thread-safe — use ThreadLocal in Selenium.
How do you take a screenshot on failure?
Implement ITestListener.onTestFailure (or a TestWatcher equivalent) and capture there; register via @Listeners or testng.xml.
How do you retry a flaky test?
Implement IRetryAnalyzer to re-run N times — use sparingly; it hides real instability.
priority vs dependsOnMethods?
priority just orders independent tests; dependsOnMethods creates a real prerequisite relationship that can skip dependents.
Why prefer independent tests?
Isolation enables safe parallelism and clear failures; chained dependencies make one failure cascade and block concurrency.
What is testng.xml?
The suite descriptor — selects classes/groups, passes parameters, and configures parallelism without recompiling.