Introducción a las pruebas en Java
Por qué importan las pruebas en Java, la pirámide de pruebas y una visión general de los frameworks de pruebas más comunes.
Las pruebas automatizadas son la forma de demostrar que el código hace lo que crees que hace, y de seguir demostrándolo a medida que el código cambia. En lugar de ejecutar el programa a mano y revisar la salida a ojo, escribes pequeños programas que ejercitan tu código y verifican los resultados automáticamente. En Java este ecosistema se construye en torno a frameworks como JUnit y Mockito, pero las ideas subyacentes —arrange, act, assert— son lo suficientemente simples como para escribirlas a mano. Este capítulo traza el panorama antes de que los capítulos posteriores profundicen en cada herramienta.
Por qué importan las pruebas automatizadas
Una prueba es una verificación pequeña y repetible de que un fragmento de código se comporta correctamente. La ganancia no está en la primera ejecución, sino en todas las ejecuciones posteriores. Una vez que un comportamiento queda capturado en una prueba, cualquier cambio que lo rompa falla de forma ruidosa e inmediata, en lugar de aparecer como un error en producción semanas después. Las pruebas también documentan la intención: una prueba bien nombrada dice lo que el código debería hacer.
// A test names a behavior, runs the code, and asserts the outcome.
@Test
void addsTwoPositiveNumbers() {
int result = Calculator.add(2, 3);
assertEquals(5, result); // fails the build if result != 5
}El objetivo es obtener retroalimentación rápida. Una suite de pruebas en verde significa que puedes refactorizar con confianza; una en rojo señala directamente lo que se rompió.
La pirámide de pruebas
Las pruebas se organizan en capas, generalmente dibujadas como una pirámide. Las pruebas unitarias están en la base: muchas, rápidas, cada una verifica una clase o método de forma aislada. Las pruebas de integración están en el medio: menos, más lentas, verifican que los componentes funcionen juntos (tu código más una base de datos, por ejemplo). Las pruebas de extremo a extremo (E2E) están en la cima: pocas, las más lentas, controlan toda la aplicación como lo haría un usuario.
| Nivel | Alcance | Velocidad | Cantidad | Herramientas Java |
|---|---|---|---|---|
| Unitaria | una clase/método | rápida (ms) | muchas | JUnit, AssertJ |
| Integración | varios componentes | media | algunas | JUnit, Testcontainers |
| Extremo a extremo | sistema completo | lenta | pocas | Selenium, REST-assured |
La forma importa: apóyate en las pruebas unitarias, baratas y rápidas, para la mayor parte de la cobertura, y reserva las pruebas E2E, lentas y frágiles, para unos pocos recorridos críticos del usuario.
El patrón arrange–act–assert
Casi toda prueba, en cualquier framework, sigue la misma estructura de tres pasos. Arrange prepara las entradas y las dependencias. Act llama al código bajo prueba. Assert verifica que el resultado coincide con lo esperado. Mantener estos pasos visualmente separados hace que una prueba sea fácil de leer y de depurar cuando falla.
@Test
void rejectsBlankUsername() {
// Arrange
UserService service = new UserService();
// Act
boolean valid = service.isValidUsername(" ");
// Assert
assertFalse(valid);
}Una aserción que falla lanza una excepción, el framework la registra y la ejecución continúa con la siguiente prueba, de modo que un comportamiento roto nunca oculta a los demás.
JUnit, el ejecutor estándar
JUnit es el framework de pruebas unitarias de facto para Java. Anotas los métodos con @Test, JUnit los descubre mediante reflexión, ejecuta cada uno e informa el resultado de aprobado/fallido. Aserciones como assertEquals, assertTrue y assertThrows son ayudantes estáticos que fallan la prueba cuando la expectativa no se cumple. Los proyectos reales ejecutan JUnit a través de una herramienta de construcción (el plugin Surefire de Maven o la tarea test de Gradle), no a mano.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void dividesNumbers() {
assertEquals(4, Calculator.divide(8, 2));
}
@Test
void throwsOnDivideByZero() {
assertThrows(ArithmeticException.class, () -> Calculator.divide(1, 0));
}
}Dado que no hay un JAR de JUnit ni una herramienta de construcción en este entorno de ejecución, el ejemplo siguiente construye la misma idea desde cero: un pequeño arnés que ejecuta verificaciones con nombre y contabiliza aprobados y fallidos, exactamente lo que @Test más assertEquals hacen internamente.
Qué extraer de la ejecución:
- Cada llamada a
assertEqualses un caso de prueba: se preparan las entradas (arrange), se actúa llamando aaddoisBlank, y se verifica el resultado (assert), reflejando exactamente lo que hace un método@Testde JUnit. - Una verificación que pasa imprime
PASSy una que falla imprimeFAILcon los valores esperado y real, que es el diagnóstico que te dan los mensajes de aserción de JUnit. - El caso deliberadamente incorrecto (
expected 10 but got 5) muestra cómo luce una prueba en rojo: el arnés continúa ejecutando las verificaciones restantes en lugar de detenerse ante el primer fallo. - El resumen contabiliza 5 en total, 4 aprobadas, 1 fallida, el mismo informe de aprobado/fallido que imprime un ejecutor de pruebas al final de una ejecución.
- Dado que una prueba falló, el programa termina con
BUILD FAILURE, demostrando por qué una sola prueba rota debería romper toda la construcción en CI.
Cómo encajan las piezas
Las herramientas de pruebas de Java se superponen unas sobre otras, desde las aserciones básicas hasta la integración completa con la construcción:
- Las aserciones (
assertEquals,assertThrows) establecen lo que debe ser verdadero. - JUnit descubre y ejecuta los métodos
@Teste informa los resultados. - Mockito proporciona colaboradores falsos para que una unidad pueda probarse de forma aislada.
- Maven o Gradle conecta la suite con la construcción, fallando la construcción ante cualquier prueba en rojo.
- CI ejecuta la construcción en cada push, de modo que el código roto nunca llega a la rama principal.
Cada capítulo posterior aborda un peldaño de esta escalera: primero las anotaciones y aserciones de JUnit, luego el mocking con Mockito, y después la integración de pruebas en Maven y Gradle. Entender dónde se ubica cada herramienta mantiene coherente toda la historia de las pruebas.