Introducción: Por qué las pruebas son la base del código Java de calidad
Cada desarrollador Java se ha enfrentado a la situación en la que, después de corregir un error, aparecen tres nuevos. Para romper este círculo vicioso, existen las pruebas. No es solo una marca en una lista de verificación, sino una disciplina profesional que ahorra nervios, dinero y la reputación del producto.
En este artículo, analizaremos tres niveles clave de pruebas: unit (pruebas unitarias), integration (pruebas de integración) y e2e (end-to-end). Los veremos con ejemplos prácticos en Java, utilizando frameworks populares: JUnit 5, Mockito, Spring Boot Test y Selenium. Aprenderás cómo construir la pirámide de pruebas correcta y no sobrecargar el proyecto con pruebas inútiles.
1. Pruebas Unitarias: Verificación de unidades atómicas de código
Las pruebas unitarias son el nivel más bajo y el más rápido. Verifican el trabajo de una sola "unidad" (generalmente un método o clase) de forma aislada de las dependencias externas (BD, API, sistema de archivos).
1.1 Herramientas y frameworks
- JUnit 5 — el estándar de facto para escribir pruebas en Java.
- Mockito — biblioteca para crear mocks y stubs.
- AssertJ — biblioteca para verificaciones fluidas (afirmaciones).
1.2 Ejemplo de prueba unitaria con JUnit 5 y Mockito
Imaginemos que tenemos un servicio OrderService que depende de un repositorio PaymentRepository. Queremos probar la lógica del servicio sin llamar a la BD real.
// Clase a probar
public class OrderService {
private final PaymentRepository paymentRepository;
public OrderService(PaymentRepository paymentRepository) {
this.paymentRepository = paymentRepository;
}
public String processOrder(Order order) {
if (order.getAmount() <= 0) {
throw new IllegalArgumentException("El monto del pedido debe ser positivo");
}
boolean success = paymentRepository.pay(order);
return success ? "Pago exitoso" : "Error de pago";
}
}
// Prueba unitaria
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private PaymentRepository paymentRepository;
@InjectMocks
private OrderService orderService;
@Test
void testProcessOrder_Success() {
// Arrange
Order order = new Order(100.0);
when(paymentRepository.pay(order)).thenReturn(true);
// Act
String result = orderService.processOrder(order);
// Assert
assertEquals("Pago exitoso", result);
verify(paymentRepository, times(1)).pay(order);
}
@Test
void testProcessOrder_NegativeAmount() {
// Arrange
Order order = new Order(-10.0);
// Act & Assert
assertThrows(IllegalArgumentException.class, () -> {
orderService.processOrder(order);
});
}
}
Mejores prácticas para pruebas unitarias:
- Una prueba, una verificación (principio FIRST: Fast, Isolated, Repeatable, Self-validating, Timely).
- Usa nombres descriptivos:
shouldReturnSuccess_whenPaymentIsApproved. - No pruebes métodos privados — prueba el contrato público.
- Cubre casos límite: null, cadenas vacías, números negativos.
2. Pruebas de Integración: Verificación de la interacción de componentes
Las pruebas de integración verifican cómo los diferentes módulos del sistema