TL;DR
- Elegí JUnit 5 para: proyectos nuevos, Spring Boot, características modernas de Java, microservicios, independencia estricta de tests
- Elegí TestNG para: suites complejas con dependencias, hooks a nivel de suite, configuración XML, Selenium con ejecución paralela
- JUnit 5 domina las descargas de Maven Central; TestNG sigue siendo fuerte en automatización enterprise
- Ambos soportan ejecución paralela, tests parametrizados e integración con CI/CD
Tiempo de lectura: 14 minutos
JUnit 5 y TestNG son los dos frameworks de testing Java dominantes, pero han divergido significativamente en filosofía y casos de uso. JUnit 5, que tuvo su release estable en 2017, se ha convertido en el framework de testing predeterminado para proyectos Spring Boot y desarrollo Java moderno — con miles de millones de descargas mensuales en Maven Central. TestNG, creado en 2004 por Cédric Beust, sigue siendo el framework elegido para automatización Selenium enterprise y suites de pruebas de integración complejas, con una fuerte audiencia en equipos que dependen de su gestión de suites basada en XML y sus características de dependencia entre tests. Según la JetBrains Developer Ecosystem Survey 2024, JUnit lo usan el 79% de los desarrolladores Java que escriben tests, mientras que TestNG representa aproximadamente el 25% — con una superposición significativa en entornos enterprise. La elección entre ellos raramente se reduce a brechas de capacidad (ambos pueden manejar la mayoría de las necesidades de testing) sino más bien al ajuste con el ecosistema, el background del equipo y las características específicas que requiere tu arquitectura de tests.
Introducción
Elegir entre TestNG y JUnit 5 (Jupiter) es una de las decisiones más críticas para equipos de automatización de pruebas en Java. Ambos frameworks han evolucionado significativamente, con JUnit (como se discute en Allure Framework: Creating Beautiful Test Reports) 5 introduciendo muchas características que anteriormente eran exclusivas de TestNG. Esta comparación comprehensiva te ayudará a tomar una decisión informada basada en características, casos de uso y estrategias de migración.
Contexto Histórico
Evolución de JUnit
- JUnit 4 (2006): Introdujo anotaciones, llevando las pruebas Java a la era moderna
- JUnit 5 (2017): Reescritura completa con arquitectura modular (Platform, Jupiter, Vintage)
Orígenes de TestNG
- Creado en 2004 por Cédric Beust, inspirado en JUnit y NUnit
- Objetivo de diseño: Abordar las limitaciones de JUnit, particularmente para pruebas de integración y funcionales
Comparación de Arquitectura
Arquitectura Modular de JUnit 5
JUnit 5 consiste en tres módulos:
<!-- JUnit (como se discute en [REST Assured: Java-Based API Testing Framework for Modern Applications](/es/blog/rest-assured-api-testing)) Platform: Base para lanzar frameworks de testing -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit (como se discute en [WebdriverIO: Extensibility, Multiremote, and Migration Guide](/es/blog/webdriverio-extensibility-multiremote-migration))-platform-launcher</artifactId>
</dependency>
<!-- JUnit Jupiter: Nuevo modelo de programación y extensión -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.1</version>
</dependency>
<!-- JUnit Vintage: Soporte para tests de JUnit 3 y JUnit 4 -->
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.10.1</version>
</dependency>
TestNG Módulo Único
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.8.0</version>
</dependency>
Comparación de Anotaciones
| Característica | TestNG | JUnit 5 | Notas |
|---|---|---|---|
| Antes de todos los tests en clase | @BeforeClass | @BeforeAll | JUnit 5 requiere método estático |
| Después de todos los tests en clase | @AfterClass | @AfterAll | JUnit 5 requiere método estático |
| Antes de cada test | @BeforeMethod | @BeforeEach | Funcionalidad similar |
| Después de cada test | @AfterMethod | @AfterEach | Funcionalidad similar |
| Configuración de suite | @BeforeSuite | N/A | Ventaja de TestNG |
| Limpieza de suite | @AfterSuite | N/A | Ventaja de TestNG |
| Grupos de tests | @Test(groups) | @Tag | Enfoque diferente |
| Ignorar test | @Test(enabled=false) | @Disabled | JUnit 5 más explícito |
| Excepción esperada | @Test(expectedExceptions) | assertThrows() | JUnit 5 más flexible |
| Timeout | @Test(timeOut) | @Timeout o assertTimeout() | Ambos funcionales |
| Tests parametrizados | @DataProvider | @ParameterizedTest | Paradigmas diferentes |
Comparación Detallada de Características
1. Tests Parametrizados
TestNG con DataProvider:
@DataProvider(name = "loginData")
public Object[][] loginDataProvider() {
return new Object[][] {
{"user1", "pass1", true},
{"user2", "wrongpass", false},
{"", "pass3", false}
};
}
@Test(dataProvider = "loginData")
public void testLogin(String username, String password, boolean shouldSucceed) {
LoginPage page = new LoginPage();
boolean result = page.login(username, password);
assertEquals(result, shouldSucceed);
}
JUnit 5 con @ParameterizedTest:
@ParameterizedTest
@CsvSource({
"user1, pass1, true",
"user2, wrongpass, false",
", pass3, false"
})
void testLogin(String username, String password, boolean shouldSucceed) {
LoginPage page = new LoginPage();
boolean result = page.login(username, password);
assertEquals(result, shouldSucceed);
}
// Alternativa: @MethodSource para datos complejos
@ParameterizedTest
@MethodSource("loginDataProvider")
void testLoginMethodSource(String username, String password, boolean shouldSucceed) {
// Implementación del test
}
static Stream<Arguments> loginDataProvider() {
return Stream.of(
Arguments.of("user1", "pass1", true),
Arguments.of("user2", "wrongpass", false),
Arguments.of("", "pass3", false)
);
}
Veredicto: JUnit 5 ofrece más fuentes integradas (@CsvSource, @ValueSource, @EnumSource, @MethodSource), mientras que el @DataProvider de TestNG es más flexible para escenarios complejos.
2. Dependencias de Tests
TestNG:
@Test
public void testLogin() {
// Test de login
}
@Test(dependsOnMethods = "testLogin")
public void testAddToCart() {
// Se ejecuta solo si testLogin pasa
}
@Test(dependsOnGroups = "sanity")
public void testCheckout() {
// Se ejecuta después de todos los tests en grupo "sanity"
}
JUnit 5:
JUnit 5 no soporta dependencias de tests por diseño. Los tests deben ser independientes. Usa @TestMethodOrder para ordenamiento:
@TestMethodOrder(OrderAnnotation.class)
class CheckoutFlowTest {
@Test
@Order(1)
void testLogin() {
// Test de login
}
@Test
@Order(2)
void testAddToCart() {
// Test de agregar al carrito
}
}
Veredicto: La gestión de dependencias de TestNG es poderosa para pruebas de integración donde el orden del flujo importa. JUnit 5 fuerza mejor aislamiento de tests.
3. Ejecución Paralela
TestNG (testng.xml):
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Parallel Suite" parallel="methods" thread-count="5">
<test name="Regression">
<classes>
<class name="com.example.LoginTest"/>
<class name="com.example.CheckoutTest"/>
</classes>
</test>
</suite>
Opciones de ejecución paralela:
parallel="methods": Cada método de test en hilo separadoparallel="classes": Cada clase de test en hilo separadoparallel="tests": Cada tag<test>en hilo separado
JUnit 5 (junit-platform.properties):
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.config.strategy = dynamic
Control programático:
@Execution(ExecutionMode.CONCURRENT)
class ParallelTest {
@Test
void test1() { }
@Test
void test2() { }
}
Veredicto: TestNG tiene configuración paralela más madura, basada en XML. La ejecución paralela de JUnit 5 es más nueva pero mejorando rápidamente.
4. Grupos de Tests y Etiquetado
TestNG:
@Test(groups = {"sanity", "regression"})
public void testCriticalFeature() {
// Código del test
}
@Test(groups = "regression")
public void testSecondaryFeature() {
// Código del test
}
Ejecutar grupos específicos vía testng.xml:
<test name="Sanity Tests">
<groups>
<run>
<include name="sanity"/>
</run>
</groups>
<classes>
<class name="com.example.AllTests"/>
</classes>
</test>
JUnit 5:
@Test
@Tag("sanity")
@Tag("regression")
void testCriticalFeature() {
// Código del test
}
@Test
@Tag("regression")
void testSecondaryFeature() {
// Código del test
}
Configuración Maven:
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<groups>sanity</groups>
</configuration>
</plugin>
Veredicto: Ambos ofrecen funcionalidad similar. El enfoque basado en XML de TestNG es más flexible para configuraciones complejas de suites de tests.
5. Prueba de Excepciones
TestNG:
@Test(expectedExceptions = IllegalArgumentException.class,
expectedExceptionsMessageRegExp = ".*invalid input.*")
public void testInvalidInput() {
service.processInput("");
}
JUnit 5:
@Test
void testInvalidInput() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> service.processInput("")
);
assertTrue(exception.getMessage().contains("invalid input"));
}
Veredicto: El enfoque de JUnit 5 es más flexible, permitiendo aserciones sobre el objeto de excepción.
6. Tests Dinámicos
Solo JUnit 5:
@TestFactory
Stream<DynamicTest> dynamicTestsFromStream() {
return Stream.of("apple", "banana", "orange")
.map(fruit -> dynamicTest(
"Test " + fruit,
() -> assertFalse(fruit.isEmpty())
));
}
Veredicto: JUnit 5 tiene soporte nativo para generación dinámica de tests en runtime. TestNG requiere soluciones personalizadas.
Comparación de Características Avanzadas
Características Únicas de TestNG
1. Configuración a Nivel de Suite:
@BeforeSuite
public void setupDatabase() {
// Se ejecuta una vez antes de toda la suite de tests
}
@AfterSuite
public void cleanupDatabase() {
// Se ejecuta una vez después de toda la suite de tests
}
2. Configuración Flexible de Tests:
@Test(invocationCount = 5, threadPoolSize = 3)
public void stressTest() {
// Se ejecuta 5 veces con 3 hilos concurrentes
}
@Test(successPercentage = 80)
public void flakyTest() {
// Pasa si 80% de las invocaciones tienen éxito
}
3. Lógica de Reintento Integrada:
public class RetryAnalyzer implements IRetryAnalyzer {
private int retryCount = 0;
private static final int maxRetryCount = 3;
@Override
public boolean retry(ITestResult result) {
if (retryCount < maxRetryCount) {
retryCount++;
return true;
}
return false;
}
}
@Test(retryAnalyzer = RetryAnalyzer.class)
public void unstableTest() {
// Se reintentará hasta 3 veces en caso de fallo
}
Características Únicas de JUnit 5
1. Tests Anidados:
@DisplayName("Tests de Carrito de Compras")
class ShoppingCartTest {
@Nested
@DisplayName("Cuando el carrito está vacío")
class EmptyCart {
@Test
void shouldHaveZeroItems() { }
@Test
void shouldHaveZeroTotal() { }
}
@Nested
@DisplayName("Cuando el carrito tiene artículos")
class NonEmptyCart {
@BeforeEach
void addItems() { }
@Test
void shouldCalculateCorrectTotal() { }
}
}
2. Ejecución Condicional de Tests:
@Test
@EnabledOnOs(OS.LINUX)
void onlyOnLinux() { }
@Test
@EnabledIfSystemProperty(named = "env", matches = "prod")
void onlyInProduction() { }
@Test
@EnabledIfEnvironmentVariable(named = "CI", matches = "true")
void onlyInCI() { }
3. Modelo de Extensión:
@ExtendWith(TimingExtension.class)
class PerformanceTest {
@Test
void fastOperation() { }
}
// Extensión personalizada
public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
@Override
public void beforeTestExecution(ExtensionContext context) {
// Iniciar temporizador
}
@Override
public void afterTestExecution(ExtensionContext context) {
// Registrar tiempo de ejecución
}
}
Matriz de Decisión
| Caso de Uso | Recomendación | Razonamiento |
|---|---|---|
| Nuevo proyecto Java | JUnit 5 | Arquitectura moderna, desarrollo activo |
| Proyecto Spring Boot | JUnit 5 | Soporte nativo Spring, mejor integración |
| Suites complejas con dependencias | TestNG | Gestión superior de dependencias |
| Grandes proyectos empresariales | TestNG | Configuración XML madura, gestión de suites |
| Testing de microservicios | JUnit 5 | Mejor modularidad, más ligero |
| Tests end-to-end de integración | TestNG | Dependencias de flujo, hooks a nivel suite |
| CI/CD con Jenkins | Ambos | Soporte igual |
| Tests Selenium WebDriver | Ambos | Capacidades iguales |
| Testing de API | Ambos | Capacidades iguales |
| Testing móvil (Appium) | TestNG | Mejor control de ejecución paralela |
«Los equipos que más dificultades tienen con esta decisión son los que la tratan como puramente técnica. En la práctica, si tu equipo ya tiene 500 tests de Selenium en TestNG y usa el suite XML para ejecución paralela en distintos entornos, cambiar a JUnit 5 es meses de migración por una ganancia marginal. Pero si estás empezando desde cero con microservicios Spring Boot, usar JUnit 5 por defecto hace que todo el stack sea consistente — Spring Test, Mockito, AssertJ funcionan sin ningún puente.» — Yuri Kan, Senior QA Lead
Guía de Migración: TestNG a JUnit 5
Paso 1: Actualizar Dependencias
Reemplazar TestNG con JUnit 5 en pom.xml:
<!-- Eliminar -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
</dependency>
<!-- Agregar -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
Paso 2: Actualizar Anotaciones
| TestNG | Equivalente JUnit 5 |
|---|---|
@BeforeClass | @BeforeAll (hacer método estático) |
@AfterClass | @AfterAll (hacer método estático) |
@BeforeMethod | @BeforeEach |
@AfterMethod | @AfterEach |
@Test(enabled=false) | @Disabled |
Paso 3: Reemplazar DataProviders
Antes (TestNG):
@DataProvider
public Object[][] testData() {
return new Object[][] {{"test1"}, {"test2"}};
}
@Test(dataProvider = "testData")
public void test(String input) { }
Después (JUnit 5):
@ParameterizedTest
@ValueSource(strings = {"test1", "test2"})
void test(String input) { }
Paso 4: Manejar Dependencias de Tests
Reemplazar dependsOnMethods con aislamiento apropiado de tests o usar @TestMethodOrder:
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrderedTests {
@Test
@Order(1)
void firstTest() { }
@Test
@Order(2)
void secondTest() { }
}
Consideraciones de Rendimiento
Tiempo de Inicio
- JUnit 5: Inicio más rápido debido a carga perezosa
- TestNG: Ligeramente más lento pero despreciable en la mayoría de casos
Velocidad de Ejecución
Ambos frameworks tienen velocidades de ejecución similares. Las diferencias suelen ser específicas del proyecto.
Huella de Memoria
- JUnit 5: Huella más pequeña, diseño modular
- TestNG: Ligeramente mayor pero incluye más características integradas
Comunidad y Ecosistema
JUnit 5
- Adopción: Ampliamente adoptado, especialmente en ecosistema Spring
- Documentación: Documentación oficial comprehensiva
- Integración: Excelente soporte IDE (IntelliJ, Eclipse, VS Code)
- Plugins: Extenso soporte de plugins Maven/Gradle
TestNG
- Adopción: Fuerte en Selenium y automatización empresarial
- Documentación: Buena documentación, muchos recursos de terceros
- Integración: Buen soporte IDE, herramientas maduras
- Plugins: Ecosistema robusto de plugins
Conclusión
Elige JUnit 5 si:
- Inicias un nuevo proyecto
- Usas Spring Boot
- Prefieres características modernas de Java
- Quieres huella de dependencia más ligera
- Valoras independencia estricta de tests
Elige TestNG si:
- Gestionas suites complejas con dependencias
- Necesitas hooks de configuración a nivel suite
- Requieres control flexible de ejecución paralela
- Trabajas con sistemas empresariales legacy
- Prefieres configuración de tests basada en XML
Ambos frameworks están listos para producción y son capaces. La elección a menudo depende de la familiaridad del equipo, infraestructura existente y requisitos específicos del proyecto. Muchas organizaciones usan exitosamente ambos frameworks en diferentes proyectos según la apropiación del caso de uso.
Recursos Oficiales
- Documentación de JUnit 5
- Documentación de TestNG
- JetBrains Developer Ecosystem Survey 2024
- Maven Central — descargas de JUnit Jupiter
See Also
- Katalon Studio: Plataforma Completa Todo-en-Uno para Automatización de Pruebas - Guía completa de Katalon Studio como solución todo-en-uno para…
- Alternativas a Postman 2025: Comparación de Bruno vs Insomnia vs Thunder Client - Comparación comprehensiva de alternativas a Postman incluyendo…
- Automatización BDD con Cucumber: Guía Completa de Pruebas Orientadas al Comportamiento - Guía completa de automatización BDD con Cucumber cubriendo…
- Locust Pruebas de Carga con Python: Guía Completa de Pruebas de Rendimiento - Guía comprehensiva de pruebas de carga con Locust cubriendo…
