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

Según el Stack Overflow Developer Survey 2024, TestNG sigue siendo uno de los top 5 frameworks de testing Java utilizados por desarrolladores profesionales, especialmente en entornos enterprise donde la automatización Selenium WebDriver requiere orquestación avanzada de tests. La investigación de JetBrains State of Developer Ecosystem 2024 encontró que el 68% de equipos Java enterprise usan TestNG para suites de tests end-to-end — principalmente por la ejecución paralela integrada, data providers y configuración XML de suite que JUnit requería implementar con plugins de terceros. A diferencia de JUnit, diseñado para unit testing, TestNG fue arquitecturado desde cero para testing de integración y E2E: dependencias de tests, agrupación, suites parametrizadas y configuración de Selenium grid son funciones nativas. Los equipos que migran 500+ tests secuenciales a ejecución paralela de TestNG típicamente ven reducciones del 60-75% en el pipeline CI — la diferencia entre loops de feedback de 2 horas y 30 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

“TestNG se convirtió en mi framework Java predeterminado después de ver lo mucho más fácil que es la ejecución paralela de Selenium comparada con JUnit. La configuración testng.xml hace trivial dividir suites entre navegadores, entornos y pools de threads — cosas que requieren runners personalizados en JUnit. Para proyectos Selenium enterprise, raramente recomiendo otra cosa.” — Yuri Kan, Senior QA Lead

FAQ

¿Qué es TestNG?

TestNG es un framework Java para testing de integración y E2E con ejecución paralela, data providers, dependencias de tests y configuración XML de suite — todo integrado sin plugins.

¿TestNG vs JUnit — cuál es mejor?

TestNG tiene más funciones E2E: paralela, dependencias, data providers, XML. JUnit 5 es mejor para unit testing. TestNG domina en proyectos Selenium según Stack Overflow 2024.

¿TestNG es gratis?

Sí. TestNG es completamente gratis y open-source bajo Apache 2.0. Todas las funciones — ejecución paralela, data providers, reportes HTML — disponibles sin costo.

¿Puede TestNG ejecutar tests en paralelo?

Configura en testng.xml con parallel=‘methods’ y thread-count. Usa ThreadLocal para WebDriver thread-safe. Reduce el tiempo de ejecución un 60-75%, de 2 horas a pipelines CI de 30 minutos.

Recursos Oficiales

Ver También