Cuando nos enfrentamos a la tarea de escribir tests para una clase que tiene múltiples propiedades y versiones, mantener el código de pruebas organizado puede convertirse en un verdadero desafío. Por ejemplo, imaginemos que tenemos que verificar el comportamiento de una clase Release con respecto a sus métodos isEmpty y size. Podríamos tener tests que validan estos métodos con un array vacío y otros que lo hacen con un array que contiene elementos. Aunque esta aproximación funciona, a medida que aumentan las combinaciones y casos, el archivo de tests puede volverse difícil de manejar.
Una solución elegante para este problema es aprovechar la capacidad de JUnit 5 para crear pruebas anidadas mediante la anotación @Nested. Esta característica nos permite estructurar los tests de forma jerárquica, agrupando pruebas relacionadas dentro de clases internas. Así, podemos separar claramente los tests que verifican arrays vacíos de aquellos que prueban arrays con contenido, mejorando la legibilidad y el mantenimiento del código.
Para implementar esto, simplemente creamos clases internas dentro de nuestra clase de test principal. Por ejemplo, podemos definir una clase interna llamada ArrayListIsEmpty que contenga un atributo ArrayList vacío y los tests correspondientes para validar que isEmpty devuelve true y que el tamaño es cero. De forma similar, otra clase interna llamada ArrayListNotEmpty puede contener una lista con elementos, como Arrays.asList("A", "B", "C"), y tests que comprueben que isEmpty devuelve false y que el tamaño es tres.
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import java.util.Arrays;
public class ReleaseTest {
@Nested
class ArrayListIsEmpty {
ArrayList<String> list = new ArrayList<>();
@Test
void isEmptyShouldReturnTrue() {
assertTrue(list.isEmpty());
}
@Test
void sizeShouldBeZero() {
assertEquals(0, list.size());
}
}
@Nested
class ArrayListNotEmpty {
ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
@Test
void isEmptyShouldReturnFalse() {
assertFalse(list.isEmpty());
}
@Test
void sizeShouldBeThree() {
assertEquals(3, list.size());
}
}
}
Es importante recordar que para que JUnit 5 reconozca estas clases internas como contenedores de pruebas, debemos anotarlas con @Nested. Si omitimos esta anotación, los tests dentro de esas clases no se ejecutarán y el framework nos indicará que no se han encontrado pruebas.
Esta estructura no solo mejora la organización, sino que también permite crear un árbol jerárquico de tests, donde cada nivel puede representar una categoría o condición específica. Además, podemos utilizar la anotación @DisplayName para personalizar los nombres que aparecen en los informes de ejecución, haciendo que la información sea aún más clara y fácil de interpretar.
Por ejemplo, podríamos añadir nombres descriptivos a nuestras clases y métodos de test para reflejar mejor su propósito:
import org.junit.jupiter.api.DisplayName;
@Nested
@DisplayName("Cuando la lista está vacía")
class ArrayListIsEmpty {
// tests...
}
@Nested
@DisplayName("Cuando la lista tiene elementos")
class ArrayListNotEmpty {
// tests...
}
De esta manera, al ejecutar los tests, veremos una estructura organizada y con nombres significativos que facilitan la comprensión del alcance y la intención de cada grupo de pruebas.
En definitiva, usar pruebas anidadas con @Nested en JUnit 5 es una práctica muy útil para mantener nuestros tests claros, organizados y fáciles de mantener, especialmente cuando trabajamos con múltiples escenarios y combinaciones en nuestras clases.