Ciclo de vida de pruebas JUnit en Java
Ciclo de vida de instancias de prueba y comportamiento por método vs. por clase en JUnit 5.
Cada prueba de JUnit 5 se ejecuta dentro de un ciclo de vida bien definido: una secuencia de hooks de configuración y limpieza que se disparan alrededor de tus métodos @Test en un orden garantizado. Entender ese orden — y la regla de que JUnit crea una nueva instancia de la clase de prueba por cada método de prueba — es lo que distingue los conjuntos de pruebas frágiles y dependientes del orden de los limpios y aislados. Este capítulo recorre las cinco anotaciones del ciclo de vida y los dos ciclos de vida de instancia que ofrece JUnit.
Las cinco anotaciones del ciclo de vida
JUnit 5 (el paquete org.junit.jupiter.api) define cuatro anotaciones de callback que rodean tus pruebas, más el propio @Test. Para un recorrido más completo de cada una, consulta anotaciones JUnit; si eres nuevo en el framework, comienza con la introducción a JUnit.
| Anotación | Se ejecuta | El método debe ser |
|---|---|---|
@BeforeAll | Una vez, antes de cualquier prueba en la clase | static (en el ciclo de vida por defecto) |
@BeforeEach | Antes de cada método @Test | instancia |
@Test | La prueba en sí | instancia |
@AfterEach | Después de cada método @Test | instancia |
@AfterAll | Una vez, después de que todas las pruebas hayan finalizado | static (en el ciclo de vida por defecto) |
Una clase de prueba con tres pruebas dispara @BeforeAll una vez, luego @BeforeEach → @Test → @AfterEach tres veces, y luego @AfterAll una vez.
import org.junit.jupiter.api.*;
class CalculatorTest {
@BeforeAll static void initSuite() { System.out.println("once, up front"); }
@BeforeEach void setUp() { System.out.println("before each test"); }
@Test void add() { Assertions.assertEquals(4, 2 + 2); }
@Test void subtract() { Assertions.assertEquals(0, 2 - 2); }
@AfterEach void tearDown() { System.out.println("after each test"); }
@AfterAll static void close() { System.out.println("once, at the end"); }
}Una instancia nueva por método de prueba
La regla más importante del ciclo de vida: por defecto, JUnit construye una nueva instancia de la clase de prueba antes de cada método de prueba. Los campos que modificas en una prueba no pueden filtrarse a otra, porque la siguiente prueba se ejecuta sobre un objeto diferente. Esto es lo que hace que las pruebas sean independientes del orden de ejecución.
class IsolationTest {
private int counter = 0; // re-initialised for every test
@Test void first() { counter++; Assertions.assertEquals(1, counter); }
@Test void second() { counter++; Assertions.assertEquals(1, counter); } // also 1, not 2
}Ambas pruebas ven counter == 1. Si JUnit reutilizara una instancia, la segunda prueba observaría 2 y pasaría o fallaría dependiendo del orden — exactamente la fragilidad que este diseño previene.
PER_METHOD vs. PER_CLASS
Puedes dejar de usar la instancia por método con @TestInstance(Lifecycle.PER_CLASS). Entonces JUnit crea una instancia para toda la clase, los campos de instancia persisten entre pruebas y — como conveniencia — @BeforeAll/@AfterAll pueden ser no static.
| Aspecto | PER_METHOD (por defecto) | PER_CLASS |
|---|---|---|
| Instancias creadas | una por @Test | una por clase |
| Estado del campo de instancia | reiniciado en cada prueba | compartido entre pruebas |
@BeforeAll/@AfterAll | debe ser static | pueden ser métodos de instancia |
| Mejor para | máximo aislamiento | configuración compartida costosa |
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.TestInstance.Lifecycle;
@TestInstance(Lifecycle.PER_CLASS)
class SharedFixtureTest {
@BeforeAll void openConnection() { /* non-static is now legal */ }
@AfterAll void closeConnection() { }
}Recurre a PER_CLASS solo cuando la configuración sea genuinamente costosa y segura de compartir. El valor por defecto te proporciona aislamiento de forma gratuita.
Las aserciones son la forma en que una prueba reporta un fallo
Un ciclo de vida existe para ejecutar aserciones. Assertions.assertEquals(expected, actual) lanza un AssertionFailedError cuando los valores difieren, lo que aborta esa prueba individual (su @AfterEach aún se ejecuta) y la marca como fallida — las otras pruebas continúan. Consulta aserciones JUnit para el conjunto completo de métodos assert*.
import static org.junit.jupiter.api.Assertions.*;
@Test void example() {
assertEquals(42, compute());
assertTrue(isReady());
assertThrows(IllegalArgumentException.class, () -> parse("bad"));
}Un ejemplo detallado: trazando el ciclo de vida manualmente
No hay un ejecutor JUnit en este playground de código, por lo que el programa siguiente modela el ciclo de vida en código JDK puro: dispara los hooks en el orden de JUnit, contrasta PER_METHOD (una nueva instancia por prueba) con PER_CLASS (una instancia compartida), y termina con un pequeño arnés de auto-verificación al estilo de assertEquals.
Lo que se puede concluir de la ejecución:
- El bloque
PER_METHODimprimeinstance#1,instance#2,instance#3para las tres pruebas, demostrando la regla por defecto de JUnit: se construye una nueva instancia de prueba para cada método@Test, por lo que ninguna prueba puede ver el estado modificado de otra. - En
PER_METHOD, cada línea[TEST]reportacounter=1, nunca2ni3. Cada instancia obtuvo su propio campo recién inicializado, razón por la que las pruebas se mantienen independientes del orden de ejecución — el beneficio principal del ciclo de vida por defecto. - El bloque
PER_CLASSreutilizainstance#1para las tres pruebas, y sucountersube1 → 2 → 3. Con una instancia compartida, el estado del campo de instancia se filtra deliberadamente entre pruebas — útil para fixtures compartidos costosos, peligroso si lo olvidas. @BeforeAlly@AfterAllaparecen exactamente una vez por bloque, envolviendo los pares@BeforeEach/@AfterEachpor prueba que se disparan tres veces — el orden exacto de anidamiento que JUnit garantiza alrededor de tus pruebas.- El arnés final imprime
PASS:para las tres verificaciones; uncheckfallido lanza unAssertionErrorcon un mensajeFAIL:, reflejando cómoAssertions.assertEqualsaborta una sola prueba con unAssertionFailedErrormientras deja que las demás continúen.
Capítulos relacionados
- Introducción a JUnit — configura JUnit 5 y ejecuta tu primera prueba.
- Anotaciones JUnit — cada anotación que da forma al ciclo de vida.
- Aserciones JUnit — cómo una prueba reporta realmente si pasa o falla.
- Pruebas parametrizadas — ejecuta un cuerpo de prueba sobre múltiples entradas.