TL;DR
- JUnit 5: Estándar de industria para unit tests, excelente integración Spring Boot, modelo de extensiones moderno
- TestNG: Más features incorporadas para testing complejo, config XML de suites, ecosistema Selenium
- Para unit tests: JUnit 5 (80%+ market share, todo developer Java lo conoce)
- Para Selenium/E2E: TestNG (grupos, paralelo por XML, reportes incorporados) — pero JUnit 5 está alcanzando
- Proyectos nuevos en 2026: JUnit 5 es la opción más segura por defecto
- Diferencia clave: TestNG = más features de fábrica; JUnit 5 = mejor extensibilidad y ecosistema
Ideal para: Developers Java eligiendo un framework de testing Omite si: No usas Java (mira pytest, Jest o RSpec)
JUnit y TestNG son los dos frameworks dominantes de testing Java. JUnit es el estándar indiscutido para unit testing — viene con cada IDE Java. Según las estadísticas de descargas de Maven Central, JUnit 5 se descarga más de 100 millones de veces al mes, siendo una de las bibliotecas Java más usadas. TestNG fue creado por Cedric Beust en 2004 porque JUnit 3 carecía de grupos, dependencias y ejecución paralela. Según la documentación de TestNG, su configuración XML de suites y ejecución paralela incorporada fueron diseñadas desde cero para automatización Selenium a gran escala. Una encuesta de JetBrains Developer Ecosystem 2025 muestra que JUnit domina unit testing con aproximadamente 80% de market share, mientras TestNG es fuerte en cerca del 35% de equipos de automatización Java. JUnit 5, lanzado en 2017 y ahora maduro, cerró la mayoría de las brechas de features. La pregunta en 2026 no es “¿cuál tiene más features?” — es “¿cuál encaja en el workflow de tu equipo y en la capa de testing?”
He usado ambos extensivamente — JUnit 5 para unit testing de microservicios a escala, TestNG para testing de Selenium grid con 200+ combinaciones de browsers.
Comparación Rápida
| Feature | JUnit 5 | TestNG |
|---|---|---|
| Primer release | 2017 (JUnit 5) | 2004 |
| Arquitectura | Modular (Platform + Jupiter) | Monolítica |
| Annotations | @Test, @BeforeEach, @Tag | @Test, @BeforeMethod, @Groups |
| Ejecución paralela | Config properties | Config XML (más granular) |
| Data-driven | @ParameterizedTest (5 fuentes) | @DataProvider |
| Agrupación tests | @Tag + filtrado | @Groups (primera clase) |
| Dependencias | @Order (ordenamiento) | dependsOnMethods (deps reales) |
| Config suite | junit-platform.properties | testng.xml (poderoso) |
| Reportes | Básicos + Allure/ExtentReports | HTML incorporados |
| Soporte Spring | @SpringBootTest (nativo) | SpringTestNG integración |
| Market share | ~80% (unit tests) | ~35% (automatización) |
Comparación de Annotations
Ciclo de Vida de Tests
| Propósito | JUnit 5 | TestNG |
|---|---|---|
| Método test | @Test | @Test |
| Antes de cada test | @BeforeEach | @BeforeMethod |
| Después de cada test | @AfterEach | @AfterMethod |
| Antes de todos en clase | @BeforeAll | @BeforeClass |
| Antes de test tag/grupo | — | @BeforeTest |
| Antes de toda la suite | — | @BeforeSuite |
TestNG tiene 5 niveles de lifecycle hooks. JUnit 5 tiene 2 niveles. Para Selenium, los niveles extra de TestNG son genuinamente útiles — @BeforeSuite inicia el grid, @BeforeTest abre el browser, @BeforeMethod navega a la página.
Ejemplos de Tests
Test JUnit 5
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("User Service Tests")
class UserServiceTest {
private UserService userService;
@BeforeEach
void setUp() {
userService = new UserService(new InMemoryUserRepository());
}
@Test
@DisplayName("Crear usuario con datos válidos")
void createUser_validData_succeeds() {
User user = userService.create("john@example.com", "John");
assertAll(
() -> assertNotNull(user.getId()),
() -> assertEquals("john@example.com", user.getEmail())
);
}
@ParameterizedTest
@CsvSource({"''", "' '", "'not-an-email'"})
void createUser_invalidEmail_throwsException(String email) {
assertThrows(InvalidEmailException.class, () ->
userService.create(email, "John")
);
}
}
Test TestNG
import org.testng.annotations.*;
import static org.testng.Assert.*;
public class UserServiceTest {
private UserService userService;
@BeforeMethod
public void setUp() {
userService = new UserService(new InMemoryUserRepository());
}
@Test(description = "Crear usuario con datos válidos")
public void createUser_validData_succeeds() {
User user = userService.create("john@example.com", "John");
assertNotNull(user.getId());
assertEquals(user.getEmail(), "john@example.com");
}
@DataProvider(name = "invalidEmails")
public Object[][] provideInvalidEmails() {
return new Object[][] {{""}, {" "}, {"not-an-email"}};
}
@Test(dataProvider = "invalidEmails",
expectedExceptions = InvalidEmailException.class)
public void createUser_invalidEmail_throwsException(String email) {
userService.create(email, "John");
}
}
Diferencias clave de sintaxis:
- JUnit 5:
assertEquals(expected, actual)— expected primero - TestNG:
assertEquals(actual, expected)— actual primero (!) - JUnit 5:
assertThrows()con lambda - TestNG: atributo
expectedExceptions - JUnit 5:
publicno necesario para métodos - TestNG:
publicrequerido
Testing Data-Driven
JUnit 5: Múltiples Fuentes
// CSV
@ParameterizedTest
@CsvSource({"2, 3, 5", "0, 0, 0", "-1, 1, 0"})
void testAddition_csv(int a, int b, int expected) {
assertEquals(expected, calc.add(a, b));
}
// Enum
@ParameterizedTest
@EnumSource(UserRole.class)
void testAllRoles(UserRole role) {
assertFalse(role.getPermissions().isEmpty());
}
// CSV File
@ParameterizedTest
@CsvFileSource(resources = "/test-data.csv", numLinesToSkip = 1)
void testFromFile(String input, String expected) {
assertEquals(expected, processor.process(input));
}
TestNG: Poder del DataProvider
@DataProvider(name = "userData")
public Object[][] provideUserData() {
return new Object[][] {
{"valid@email.com", true},
{"", false}
};
}
// DataProvider paralelo
@DataProvider(name = "browsers", parallel = true)
public Object[][] provideBrowsers() {
return new Object[][] {{"chrome"}, {"firefox"}, {"edge"}};
}
@Test(dataProvider = "browsers")
public void testCrossBrowser(String browser) {
WebDriver driver = createDriver(browser);
// test corre en paralelo para cada browser
}
Veredicto: JUnit 5 tiene más fuentes incorporadas (CSV, Enum, Method, File). El DataProvider de TestNG es más simple para objetos complejos y soporta parallel = true nativamente. Para testing cross-browser con Selenium, el DataProvider paralelo de TestNG es difícil de replicar en JUnit.
Ejecución Paralela
JUnit 5
# junit-platform.properties
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism = 4
TestNG
<!-- testng.xml - control granular -->
<suite name="Full Suite" parallel="tests" thread-count="4">
<test name="Chrome Tests" parallel="methods" thread-count="2">
<parameter name="browser" value="chrome"/>
<classes>
<class name="com.example.LoginTest"/>
</classes>
</test>
<test name="Firefox Tests" parallel="methods" thread-count="2">
<parameter name="browser" value="firefox"/>
<classes>
<class name="com.example.LoginTest"/>
</classes>
</test>
</suite>
Veredicto: La config XML de TestNG es más expresiva. Puedes definir exactamente qué tests corren en paralelo con qué browsers y parámetros — sin cambiar código Java.
Integración Selenium
TestNG + Selenium (Patrón Industrial)
public class BaseTest {
protected WebDriver driver;
@Parameters({"browser"})
@BeforeMethod
public void setUp(@Optional("chrome") String browser) {
driver = DriverFactory.create(browser);
}
@AfterMethod
public void tearDown(ITestResult result) {
if (result.getStatus() == ITestResult.FAILURE) {
Screenshot.capture(driver, result.getName());
}
driver.quit();
}
}
Mi recomendación: Para proyectos Selenium, TestNG todavía tiene ventaja. Su config XML, @Parameters, sistema de listeners y acceso a ITestResult facilitan la gestión de tests de browser.
Comparación de Reportes
| Feature | JUnit 5 | TestNG |
|---|---|---|
| Reporte HTML incorporado | No (necesita Allure) | Sí (test-output/index.html) |
| Jerarquía suite/test/method | Via Allure | Incorporada |
| Auto-screenshots | Setup manual | Via listener |
| XML para CI | JUnit XML | Incorporado |
Veredicto: TestNG gana out-of-the-box. JUnit 5 + Allure produce mejores reportes, pero requiere setup.
Guía de Migración
TestNG → JUnit 5
| TestNG | JUnit 5 |
|---|---|
@BeforeMethod | @BeforeEach |
@AfterMethod | @AfterEach |
@BeforeClass | @BeforeAll |
@DataProvider | @ParameterizedTest + @MethodSource |
@Test(groups="smoke") | @Test + @Tag("smoke") |
@Test(dependsOnMethods) | @Order + @TestMethodOrder |
@Test(expectedExceptions) | assertThrows() |
assertEquals(actual, exp) | assertEquals(exp, actual) (!!) |
Cuidado: El cambio de orden de parámetros en assertEquals es la causa #1 de bugs en migración.
Estimación de esfuerzo:
- 100 tests: ~3 días
- 500 tests: ~2 semanas
- 1000+ tests: ~4 semanas (2 developers)
Matriz de Decisión
“En la práctica, la elección entre TestNG y JUnit 5 se reduce a una pregunta: ¿estás escribiendo unit tests para código de aplicación, o tests de integración/E2E para una capa UI o API? Para unit tests, JUnit 5 es el estándar por defecto — cada tutorial de Spring Boot lo usa. Para automatización Selenium con ejecución paralela cross-browser, la configuración XML de suites de TestNG sigue ofreciendo más flexibilidad con menos código personalizado. No migraría una suite de automatización Selenium establecida de TestNG a JUnit 5 a menos que el equipo tuviera una razón específica y convincente.” — Yuri Kan, Senior QA Lead
| Tu Situación | Recomendación |
|---|---|
| Proyecto Java nuevo, unit tests | JUnit 5 — estándar de industria |
| Aplicación Spring Boot | JUnit 5 — integración nativa |
| Automatización Selenium/WebDriver | TestNG — mejor paralelo/grupos/reportes |
| Proyecto TestNG existente | Mantener TestNG — costo migración > beneficio |
| Equipo QA enterprise | TestNG — config XML, gestión de suites sin código |
| Microservicios | JUnit 5 — más simple, setup más rápido |
IA en Java Testing
Las herramientas de IA en 2026 funcionan bien con ambos frameworks.
Lo que la IA hace bien:
- Generar métodos de test desde código de producción
- Crear DataProviders / ParameterizedTests desde requerimientos
- Convertir entre sintaxis TestNG y JUnit 5 (incluyendo orden de assertions)
- Escribir Extensions custom (JUnit 5) o Listeners (TestNG)
Lo que necesita humanos:
- Decidir granularidad de tests (límite unit vs integración)
- Identificar edge cases desde conocimiento de dominio
- Decisiones de arquitectura de tests (balance de pirámide)
Prompt útil:
Convierte esta clase de test TestNG a JUnit 5. Presta atención al orden de parámetros en assertEquals (intercambia actual/expected), reemplaza @DataProvider con @ParameterizedTest, y reemplaza groups con @Tag. Mantén la misma cobertura.
FAQ
¿Es TestNG mejor que JUnit?
TestNG tiene más features incorporadas para automatización compleja: hooks de lifecycle granulares (@BeforeSuite hasta @BeforeMethod), ejecución paralela por XML, dependencias de tests y reportes nativos. JUnit 5 tiene mejor modelo de extensiones, ecosistema más amplio y mejor soporte IDE. Para unit testing, JUnit 5 gana. Para automatización Selenium con gestión compleja de suites, TestNG aún tiene ventaja.
¿TestNG o JUnit para Selenium?
TestNG es tradicionalmente preferido para Selenium por ejecución paralela por XML entre browsers, @DataProvider(parallel=true) para tests cross-browser, dependencias de métodos y reportes HTML incorporados. JUnit 5 puede hacer todo esto con extensions, pero requiere más código custom. Si tu equipo ya conoce TestNG — quédate con él. Para equipos nuevos — ambos sirven.
¿Puedo usar TestNG y JUnit juntos?
Técnicamente sí, usando módulos Maven separados — JUnit para unit tests en src/test/, TestNG para tests de integración en módulo separado. Esto agrega complejidad de build. La mayoría de equipos eligen un framework para consistencia.
¿Cuál es más popular en 2026?
JUnit domina unit testing con ~80% de market share entre developers Java. TestNG mantiene ~35% en automatización/QA. Proyectos nuevos eligen cada vez más JUnit 5 para todo. TestNG retiene fuerte adopción en equipos QA enterprise, especialmente con frameworks Selenium establecidos.
¿TestNG está muerto en 2026?
No. TestNG 7.10+ se mantiene activamente, tiene usuarios enterprise dedicados y sigue siendo estándar en muchos equipos Selenium. Pero el crecimiento es más lento que JUnit 5. Si empiezas de cero — JUnit 5 es la apuesta más segura a largo plazo.
¿Qué tan difícil es migrar de TestNG a JUnit 5?
Esfuerzo moderado. El mapeo de annotations es directo (ver tabla de migración). Las partes difíciles: convertir @DataProvider a @ParameterizedTest, reescribir config testng.xml como properties/extensions, y corregir el orden de parámetros en assertions. Presupuesta 2 semanas para suite de 500 tests con un developer dedicado.
Fuentes: La documentación oficial de TestNG cubre configuración XML de suites, ejecución paralela y patrones DataProvider. La documentación de JUnit 5 proporciona la referencia completa de API para los módulos Jupiter, Platform y Vintage.
Ver También
- Tutorial TestNG - Guía completa de TestNG
- TestNG vs JUnit 5 Deep Dive - Comparación extendida
- Tutorial Selenium - Básicos de WebDriver para Java
- Selenium Grid 4 - Setup de testing distribuido
- Allure Framework - Reportes avanzados para ambos frameworks
- Paralelización de Tests en CI/CD - Estrategias de ejecución paralela
- Jenkins Pipeline - Integración CI/CD
- Tutorial de Automatización - Fundamentos de automatización
- Pirámide de Automatización - Dónde encajan unit y E2E tests
