TL;DR
- TestNG es un poderoso framework Java con rico soporte de anotaciones
- Anotaciones clave: @Test, @BeforeMethod, @AfterMethod, @DataProvider
- Configura test suites vía testng.xml para agrupación, paralelización, parámetros
- Data providers habilitan data-driven testing con múltiples conjuntos de datos
- Integración perfecta con Selenium WebDriver para automatización de navegador
Ideal para: Desarrolladores Java, automatización Selenium, frameworks de testing enterprise Omite si: Usas Kotlin (prefiere Kotest) o tests unitarios simples (JUnit es suficiente) Tiempo de lectura: 15 minutos
Tus tests de Selenium se ejecutan secuencialmente. 500 tests toman 2 horas. El pipeline CI/CD bloquea cada PR. Los desarrolladores ignoran resultados de tests porque el feedback llega muy tarde.
TestNG resuelve problemas de ejecución de tests. Ejecución paralela, configuración flexible, data-driven testing — todo integrado. Sin frameworks adicionales necesarios.
Este tutorial cubre TestNG desde setup hasta funciones avanzadas — todo para construir automatización de tests escalable.
¿Qué es TestNG?
TestNG (Test Next Generation) es un framework de testing para Java inspirado en JUnit y NUnit. Va más allá del testing unitario simple con funciones diseñadas para testing de integración y end-to-end.
Por qué TestNG:
- Anotaciones ricas — @Test, @Before/@After en múltiples niveles
- Ejecución paralela — soporte integrado para testing concurrente
- Data providers — data-driven testing elegante
- Dependencias de tests — control del orden de ejecución
- Configuración XML — organización flexible de suites
- Reportes HTML — reportes integrados
Instalación
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()
}
Anotaciones Básicas
@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) // Saltar este test
public void testDivision() {
// Aún no implementado
}
@Test(expectedExceptions = ArithmeticException.class)
public void testDivideByZero() {
Calculator calc = new Calculator();
calc.divide(10, 0);
}
}
@Before y @After
import org.testng.annotations.*;
public class DatabaseTest {
private Connection connection;
@BeforeSuite
public void setupSuite() {
// Se ejecuta una vez antes de todos los tests en suite
System.out.println("Iniciando test suite");
}
@BeforeClass
public void setupClass() {
// Se ejecuta una vez antes del primer test en clase
connection = Database.connect();
}
@BeforeMethod
public void setupMethod() {
// Se ejecuta antes de cada método @Test
connection.beginTransaction();
}
@Test
public void testInsert() {
// Código del test
}
@Test
public void testUpdate() {
// Código del test
}
@AfterMethod
public void teardownMethod() {
// Se ejecuta después de cada método @Test
connection.rollback();
}
@AfterClass
public void teardownClass() {
// Se ejecuta una vez después del último test en clase
connection.close();
}
@AfterSuite
public void teardownSuite() {
// Se ejecuta una vez después de todos los tests en suite
System.out.println("Suite completado");
}
}
Orden de Ejecución
@BeforeSuite
@BeforeClass
@BeforeMethod
@Test (método 1)
@AfterMethod
@BeforeMethod
@Test (método 2)
@AfterMethod
@AfterClass
@AfterSuite
Data Providers
Data Provider Básico
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 falló para: " + email);
}
}
Data Provider Externo
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][]);
}
}
// Uso en otra clase
public class UserTest {
@Test(dataProvider = "csvData", dataProviderClass = TestDataProvider.class)
public void testUserCreation(String name, String email, String role) {
// Test con datos CSV
}
}
Configuración de Tests (testng.xml)
Configuración Básica
<?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>
Parámetros
<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 se ejecuta con navegador configurado
}
}
Ejecución Paralela
Opciones de Configuración
<!-- Paralelo por métodos -->
<suite name="Parallel Suite" parallel="methods" thread-count="4">
<test name="Tests">
<classes>
<class name="com.example.tests.LoginTest"/>
</classes>
</test>
</suite>
<!-- Paralelo por clases -->
<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>
WebDriver Thread-Safe
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 es thread-safe
}
}
Grupos de Tests
Definiendo Grupos
public class UserTest {
@Test(groups = {"smoke", "regression"})
public void testUserLogin() {
// Test crítico - se ejecuta en ambos grupos
}
@Test(groups = {"regression"})
public void testUserProfile() {
// Solo en regression
}
@Test(groups = {"regression", "slow"})
public void testUserReports() {
// Test lento
}
}
Ejecutando Grupos
<suite name="Smoke Suite">
<test name="Smoke Tests">
<groups>
<run>
<include name="smoke"/>
</run>
</groups>
<packages>
<package name="com.example.tests.*"/>
</packages>
</test>
</suite>
<!-- Excluir tests lentos -->
<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>
Dependencias de Tests
public class OrderTest {
@Test
public void testLogin() {
// Login primero
}
@Test(dependsOnMethods = {"testLogin"})
public void testAddToCart() {
// Se ejecuta después de login exitoso
}
@Test(dependsOnMethods = {"testAddToCart"})
public void testCheckout() {
// Se ejecuta después de agregar al carrito
}
@Test(dependsOnMethods = {"testCheckout"})
public void testPayment() {
// Paso final
}
}
Integración con Selenium
Ejemplo Completo
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"));
}
}
TestNG con Asistencia de IA
Las herramientas de IA pueden ayudar a escribir y optimizar tests de TestNG.
Lo que la IA hace bien:
- Generar métodos de test desde requerimientos
- Crear data providers con edge cases
- Sugerir patrones de assertions
- Convertir tests de JUnit a TestNG
Lo que aún necesita humanos:
- Entender lógica de negocio
- Elegir granularidad de tests
- Balancear cobertura vs mantenimiento
- Depurar tests inestables
FAQ
¿Qué es TestNG?
TestNG (Test Next Generation) es un framework de testing Java inspirado en JUnit y NUnit. Provee funciones poderosas más allá del testing unitario simple: anotaciones para gestión de ciclo de vida, ejecución paralela de tests, data-driven testing vía data providers, dependencias de tests y configuración XML flexible para organizar test suites. Es ampliamente usado con Selenium para automatización de navegador.
¿TestNG vs JUnit — cuál es mejor?
TestNG ofrece más funciones integradas: ejecución paralela, dependencias de tests, data providers y configuración XML flexible. JUnit 5 ha agregado muchas funciones similares, reduciendo la brecha. TestNG sigue siendo popular para proyectos Selenium por su configuración de suites XML y ecosistema establecido. Para proyectos nuevos, ambos funcionan bien — elige según familiaridad del equipo.
¿TestNG es gratis?
Sí, TestNG es completamente gratis y open-source bajo licencia Apache 2.0. No hay versiones pagas, ediciones enterprise ni funciones premium. Toda la funcionalidad — ejecución paralela, data providers, reportes — está disponible sin costo.
¿Puede TestNG ejecutar tests en paralelo?
Sí, TestNG tiene excelente soporte integrado de ejecución paralela. Configura el paralelismo a nivel de método, clase o test vía testng.xml. Establece el conteo de threads según las capacidades de tu máquina. Para tests de Selenium, usa ThreadLocal para asegurar instancias de WebDriver thread-safe.
Recursos Oficiales
Ver También
- Selenium Tutorial - Fundamentos de WebDriver
- JUnit vs TestNG - Comparación de frameworks
- REST Assured Tutorial - Java API testing
- CI/CD Testing Guide - Integración con pipelines
