W3docs

Introducción a JUnit en Java

Qué es JUnit, cómo añadirlo a un proyecto Java y cómo escribir tu primer test con JUnit.

JUnit es el framework estándar de facto para escribir pruebas automatizadas en Java. Un test es simplemente un método pequeño que ejecuta una parte de tu código y verifica que se comportó como se esperaba; el trabajo de JUnit es descubrir esos métodos, ejecutar cada uno de forma aislada, comprobar las aserciones e informar cuáles pasaron y cuáles fallaron. La generación actual, JUnit 5 (también llamada JUnit Jupiter), se distribuye como un conjunto de pequeñas librerías que añades a tu build — no forma parte del JDK — y potencia el paso mvn test / gradle test que es la puerta de entrada a la CI de casi cualquier proyecto Java.

Por qué un framework de testing

Podrías verificar el código manualmente con métodos main y println — pero eso escala mal. Un framework te da cuatro cosas que de otro modo tendrías que reconstruir tú mismo:

  • Descubrimiento — encuentra automáticamente cada método @Test; nunca tienes que mantener una lista.
  • Aislamiento — cada test obtiene un fixture nuevo, para que un test no pueda corromper a otro.
  • Aserciones — un vocabulario rico (assertEquals, assertThrows, …) que produce mensajes de fallo precisos.
  • Informes — un resumen uniforme de pasa/falla que la herramienta de build y el IDE entienden.

Haz esto bien una vez y los tests se vuelven baratos de escribir, que es el punto principal: los tests baratos se escriben, y el código que está testeado puede modificarse sin miedo.

Añadir JUnit a un proyecto

JUnit 5 es una dependencia, declarada en tu archivo de build. Con Maven, el agregador junit-jupiter incluye la API (para compilar) y el motor (para ejecutar):

<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>5.11.3</version>
  <scope>test</scope>
</dependency>

Con Gradle son dos líneas más el interruptor useJUnitPlatform():

dependencies {
  testImplementation 'org.junit.jupiter:junit-jupiter:5.11.3'
}
test {
  useJUnitPlatform()
}

Los fuentes de test viven bajo src/test/java, reflejando el paquete de la clase que prueban. El scope test mantiene JUnit fuera de tu artefacto de producción.

Tu primer test

Un test de JUnit es un método ordinario anotado con @Test. Dentro de él llamas al código bajo prueba y realizas aserciones sobre el resultado. Aquí tienes una clase Calculator y una clase de test para ella:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class CalculatorTest {

  private final Calculator calc = new Calculator();

  @Test
  void addReturnsSum() {
    assertEquals(5, calc.add(2, 3));
  }

  @Test
  void divideByZeroThrows() {
    assertThrows(ArithmeticException.class, () -> calc.divide(1, 0));
  }
}

Observa el patrón que sigue cada test — Arrange (preparar) el fixture, Act (actuar) llamando al método, Assert (verificar) el resultado. Los métodos de test son de acceso de paquete (no se necesita public en JUnit 5) y devuelven void. Ejecuta mvn test y el build pasa a verde solo si todas las aserciones se cumplen.

Las anotaciones y aserciones que más usarás

La superficie de JUnit es pequeña. Estos pocos miembros cubren la gran mayoría de los tests reales:

MiembroPaquete / clasePropósito
@Testorg.junit.jupiter.apiMarca un método como test
@BeforeEach / @AfterEachorg.junit.jupiter.apiSe ejecuta antes/después de cada test (preparar / limpiar fixtures)
@BeforeAll / @AfterAllorg.junit.jupiter.apiSe ejecuta una vez antes/después de todos los tests de la clase
@DisplayNameorg.junit.jupiter.apiUn nombre legible para los informes
@Disabledorg.junit.jupiter.apiOmite temporalmente un test
assertEquals(exp, act)AssertionsFalla si los dos no son iguales
assertTrue / assertFalseAssertionsFalla si un boolean no se cumple
assertThrows(type, exec)AssertionsFalla si la lambda no lanza esa excepción
assertNull / assertNotNullAssertionsFalla si la nulabilidad es incorrecta

@BeforeEach es lo que le da a cada test un estado limpio — JUnit construye una instancia nueva de la clase de test para cada @Test, luego ejecuta la configuración, por lo que el estado nunca se filtra entre tests.

Lo que JUnit hace por ti, en un archivo ejecutable

El ejecutor de código aquí no tiene JUnit en su classpath (es una librería externa, no parte del JDK), por lo que el ejemplo siguiente reimplementa el bucle principal de JUnit en Java puro: un fixture recreado antes de cada test, un pequeño conjunto de helpers assertXxx, una lista de métodos de test ejecutados de forma independiente y un recuento de pasa/falla al final. Esta es exactamente la maquinaria que JUnit automatiza — verla al descubierto hace que el framework real resulte obvio. Un test falla a propósito para que puedas ver cómo se ve el rojo.

java— editable, runs on the server

Lo que hay que extraer de la ejecución:

  • Los tres tests correctos imprimen PASS y el cuarto imprime FAIL deliberatelyFailing -> expected <10> but was <5> — un mensaje de fallo preciso, no solo "un test falló." Esa diferencia (expected … but was …) es exactamente lo que el assertEquals de JUnit te proporciona, y es lo que hace que un test rojo sea diagnosticable de un vistazo.
  • setUp() se ejecuta antes de cada test, por lo que calc es una instancia nueva de Calculator cada vez. Este es el contrato de @BeforeEach: los tests están aislados, y el orden en que se ejecutan nunca puede importar porque ninguno de ellos comparte estado mutable.
  • divideThrowsOnZero pasa verificando que una excepción se lanza — assertThrows convierte "esto debería fallar" en una aserción positiva de primera clase en lugar de un frágil try/catch. Las excepciones esperadas son comportamiento que vale la pena probar, no errores que ignorar.
  • El recuento final — Tests run: 4, Passed: 3, Failed: 1, Assertions: 5 — es el informe. Un test fallido de cuatro aún cambia todo el build a RED; la CI trata cualquier fallo como una parada, por eso una suite en verde es significativa.
  • Nada aquí importó JUnit, sin embargo la forma es idéntica: descubrimiento de métodos anotados, configuración por test, aserciones, resumen. El valor de JUnit es que automatiza este bucle (y añade descubrimiento, paralelismo, tests parametrizados e integración con el IDE) para que tú solo escribas los cuerpos de los tests.

Qué cubre el resto de esta parte

Esta parte profundiza en el bucle central que acabas de ver:

  • Anotaciones de JUnit@Test, @DisplayName, @Disabled y el resto del conjunto de marcadores.
  • El ciclo de vida del test — cómo @BeforeEach / @AfterEach / @BeforeAll / @AfterAll le dan a cada test un fixture limpio.
  • Aserciones — el catálogo completo de assertXxx, incluyendo assertThrows para pruebas de excepciones.
  • Tests parametrizados — ejecutar un cuerpo de test con muchas entradas en lugar de copiar y pegar casos.
  • Mocking con Mockito — reemplazar los colaboradores de una clase con sustitutos para que un test unitario siga siendo un test unitario.

Si quieres tener el panorama general de por qué importan las pruebas automatizadas antes de sumergirte en la API, consulta Testing en Java. De lo contrario, el siguiente capítulo comienza donde empieza toda suite: definir una clase de test y ejecutarla.

Práctica

Práctica
En JUnit 5, ¿qué garantiza anotar un método con @BeforeEach sobre el fixture del test?
En JUnit 5, ¿qué garantiza anotar un método con @BeforeEach sobre el fixture del test?
Was this page helpful?