Back to cheat sheets

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.
Where TestNG is richer: its 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.

AnnotationRuns
@BeforeSuite / @AfterSuiteonce around the whole suite
@BeforeTest / @AfterTestaround a <test> tag in testng.xml
@BeforeClass / @AfterClassonce per class
@BeforeMethod / @AfterMethodbefore/after every @Test method
@Testthe actual test
public class LoginTest {
  @BeforeMethod public void setUp()    { driver = new ChromeDriver(); }
  @Test         public void canLogin() { /* ... */ }
  @AfterMethod  public void tearDown() { driver.quit(); }
}
Maps to: JUnit 5 @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 call assertAll(); 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 now
Maps to: JUnit 5 assertAll(...), 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);
}
Maps to: JUnit 5 @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 from testng.xml or the CLI. Like JUnit 5 @Tag, NUnit [Category], pytest markers.
  • Priority@Test(priority = 1) orders tests (default 0); lower runs first.
  • DependenciesdependsOnMethods / dependsOnGroups skip 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() { /* ... */ }
Note for interviews: heavy use of 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" with thread-count sets 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.
Maps to: JUnit 5 parallel execution (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 IRetryAnalyzer to 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.xml
Maps to: JUnit 5 extensions (TestWatcher), 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:

ConceptTestNGJUnit 5NUnitpytest
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 / groupgroups@Tag[Category]markers
skip@Test(enabled=false)@Disabled[Ignore]@skip
paralleltestng.xmlproperties file[Parallelizable]pytest-xdist
One-liner: TestNG and JUnit 5 are the two Java options — pick TestNG for built-in suite-level config/parallelism and richer data providers, JUnit 5 for the modern default and tighter ecosystem integration.

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.