Saltar al contenido principal

Capítulo 1: Introducción a las Pruebas

Introducción​

Antes de escribir su primera prueba, es crucial comprender por qué y cómo probar de manera efectiva. Este capítulo sienta las bases de una estrategia de pruebas profesional.


🎯 Objetivos del Capítulo​

Al final de este capítulo, usted sabrá:

  1. ✅ Por qué las pruebas son esenciales
  2. ✅ Distinguir los diferentes tipos de pruebas
  3. ✅ Comprender la pirámide de pruebas
  4. ✅ Configurar su proyecto para las pruebas
  5. ✅ Conocer las buenas prácticas

1. ¿Por qué Probar?​

El Costo de los Errores​

Momento de DetecciónCosto RelativoImpacto
Durante el desarrollo1xNinguno
En fase de pruebas10xRetraso
En producción100xCliente impactado, urgencia

Beneficios de las Pruebas​

✅ Confianza: El código hace lo que debe hacer
✅ Regresión: Detecta errores introducidos por cambios
✅ Documentación: Las pruebas explican el comportamiento esperado
✅ Diseño: Escribir pruebas fuerza un buen diseño (SOLID)
✅ Refactorización: Modificar el código sin miedo
✅ Despliegue: CI/CD automatizado

Sin Pruebas vs Con Pruebas​

// Sin pruebas 😰
public void deployToProduction() {
// "Espero que funcione..."
// "Ya veremos en producción..."
// "Crucemos los dedos..."
}

// Con pruebas 😎
public void deployToProduction() {
// ✅ 347 tests passed
// ✅ Coverage: 85%
// ✅ All quality gates passed
// → ¡Despliegue con confianza!
}

2. Tipos de Pruebas​

Visión General​

Pruebas Unitarias​

Qué: Probar una unidad de código en aislamiento (método, clase)

Características:

  • Rápidas (milisegundos)
  • Aisladas (sin BDD, sin red)
  • Numerosas (mayoría de las pruebas)
@Test
void shouldCalculatePriceWithDiscount() {
// Prueba ÚNICAMENTE la lógica de cálculo
PriceCalculator calculator = new PriceCalculator();

double result = calculator.applyDiscount(100.0, 20);

assertEquals(80.0, result);
}

Pruebas de Integración​

Qué: Probar varios componentes juntos

Características:

  • Más lentas (segundos)
  • Usan recursos reales (BDD, etc.)
  • Menos numerosas
@SpringBootTest
class OrderServiceIntegrationTest {

@Autowired
private OrderService orderService;

@Test
void shouldSaveOrderToDatabase() {
// Prueba el servicio + repositorio + BDD
Order order = orderService.createOrder(/*...*/);

assertNotNull(order.getId());
}
}

Pruebas End-to-End (E2E)​

Qué: Probar la aplicación completa como un usuario

Características:

  • Lentas (minutos)
  • Entorno completo
  • Pocas
// Con Selenium por ejemplo
@Test
void userShouldBeAbleToCompleteCheckout() {
browser.navigate("/products");
browser.click("#add-to-cart");
browser.click("#checkout");
browser.fill("#address", "123 Main St");
browser.click("#confirm");

assertThat(browser.getText("#confirmation"))
.contains("Order confirmed");
}

3. La Pirámide de Pruebas​

Estructura Ideal​

                    /\
/ \
/ E2E\ Pocas, lentas, frágiles
/ \
/────────\
/Integración\ Moderado
/ \
/────────────────\
/ Pruebas Unitarias \ Muchas, rápidas, estables
/____________________\

Proporciones Recomendadas​

TipoProporciónTiempo de Ejecución
Unitarias70%~5 segundos
Integración20%~30 segundos
E2E10%~5 minutos

Anti-Patrón: El Cono de Helado​

            ____________________
/ Muchas E2E \ ❌ Lentas, frágiles
/────────────────────────\
/ Pocas de integración \
/────────────────────────────\
/ Pocas pruebas \
/ unitarias \
/──────────────────────────────────\

❌ MALO: Pruebas lentas, mantenimiento difícil

4. TDD y BDD​

TDD (Test-Driven Development)​

Escribir las pruebas ANTES del código:

// Paso 1: RED - La prueba falla (el método no existe)
@Test
void shouldReturnEmptyListWhenNoProducts() {
ProductService service = new ProductService();
List<Product> products = service.findAll();
assertTrue(products.isEmpty());
}

// Paso 2: GREEN - Implementar lo mínimo
public List<Product> findAll() {
return new ArrayList<>();
}

// Paso 3: REFACTOR - Mejorar si es necesario

BDD (Behavior-Driven Development)​

Pruebas que describen el comportamiento en lenguaje natural:

// Formato Given-When-Then
@Test
void givenNewUser_whenRegister_thenAccountIsCreated() {
// Given (Contexto)
RegistrationRequest request = new RegistrationRequest(
"john", "john@email.com", "password123"
);

// When (Acción)
User user = userService.register(request);

// Then (Resultado esperado)
assertNotNull(user.getId());
assertEquals("john", user.getUsername());
assertTrue(user.isEnabled());
}

5. Configuración Maven​

Dependencias de Prueba​

<!-- pom.xml -->
<dependencies>
<!-- Spring Boot Test (incluye JUnit 5, Mockito, AssertJ) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- H2 para las pruebas -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>

<!-- Security Test (opcional) -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Lo que incluye spring-boot-starter-test​

spring-boot-starter-test incluye:
├── JUnit 5 (Jupiter)
├── Mockito
├── AssertJ
├── Hamcrest
├── JSONPath
├── JsonAssert
└── Spring Test

Estructura de Archivos de Prueba​

src/
├── main/
│ └── java/
│ └── com/example/
│ ├── service/
│ │ └── ProductService.java
│ └── repository/
│ └── ProductRepository.java
└── test/
└── java/
└── com/example/
├── service/
│ └── ProductServiceTest.java ← Pruebas unitarias
├── repository/
│ └── ProductRepositoryTest.java ← Pruebas de integración
└── controller/
└── ProductControllerTest.java ← Pruebas API

6. Configuración de Pruebas​

application-test.properties​

# src/test/resources/application-test.properties

# Base de datos H2 en memoria
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop

# Desactivar logs verbosos
logging.level.org.hibernate.SQL=WARN
spring.jpa.show-sql=false

# Desactivar Flyway para las pruebas (opcional)
spring.flyway.enabled=false

Activar el Perfil de Pruebas​

@SpringBootTest
@ActiveProfiles("test") // ← Usa application-test.properties
class MyIntegrationTest {
// ...
}

7. Ejecutar las Pruebas​

Vía Maven​

# Todas las pruebas
mvn test

# Pruebas con reporte
mvn test jacoco:report

# Una prueba específica
mvn test -Dtest=ProductServiceTest

# Un método específico
mvn test -Dtest=ProductServiceTest#shouldFindProductById

# Saltar las pruebas (¡evitar!)
mvn install -DskipTests

Vía el IDE​

IntelliJ IDEA:
- Clic derecho en clase/método → Run Test
- Ctrl+Shift+F10 (Windows)
- Ctrl+Shift+R (Mac)

VS Code:
- Icono "Run Test" encima de @Test
- Test Explorer en la barra lateral

8. Buenas Prácticas​

Convención de Nombres​

// Formato recomendado: should_ExpectedBehavior_When_StateUnderTest
@Test
void should_ReturnEmpty_When_NoProductsExist() { }

// O: given_When_Then
@Test
void given_ValidProduct_When_Save_Then_IdIsGenerated() { }

// Evitar:
@Test
void test1() { } // ❌ No descriptivo
@Test
void testProductService() { } // ❌ Demasiado vago

Patrón AAA (Arrange-Act-Assert)​

@Test
void shouldCalculateTotalPrice() {
// Arrange (Preparar)
ShoppingCart cart = new ShoppingCart();
cart.addItem(new Item("Laptop", 1000.0));
cart.addItem(new Item("Mouse", 50.0));

// Act (Ejecutar)
double total = cart.calculateTotal();

// Assert (Verificar)
assertEquals(1050.0, total);
}

Una Prueba = Un Concepto​

// ❌ MALO: Prueba varias cosas
@Test
void testProduct() {
Product p = new Product("Test", 100.0);
assertEquals("Test", p.getName());
assertEquals(100.0, p.getPrice());
p.applyDiscount(10);
assertEquals(90.0, p.getPrice());
assertThrows(Exception.class, () -> p.applyDiscount(-5));
}

// ✅ BIEN: Una prueba por comportamiento
@Test
void shouldCreateProductWithNameAndPrice() {
Product p = new Product("Test", 100.0);
assertEquals("Test", p.getName());
assertEquals(100.0, p.getPrice());
}

@Test
void shouldApplyDiscountCorrectly() {
Product p = new Product("Test", 100.0);
p.applyDiscount(10);
assertEquals(90.0, p.getPrice());
}

@Test
void shouldRejectNegativeDiscount() {
Product p = new Product("Test", 100.0);
assertThrows(IllegalArgumentException.class,
() -> p.applyDiscount(-5));
}

9. Resumen​

Puntos Clave​

ConceptoDescripción
Prueba UnitariaPrueba una unidad en aislamiento
Prueba IntegraciónPrueba varios componentes juntos
Prueba E2EPrueba la aplicación completa
TDDPruebas escritas antes del código
AAAArrange-Act-Assert

Pirámide de Pruebas​

10% E2E (lentas, frágiles)
20% Integración (moderadas)
70% Unitarias (rápidas, numerosas)

Checklist​

✅ spring-boot-starter-test en el pom.xml
✅ application-test.properties configurado
✅ Pruebas en src/test/java
✅ Nombres descriptivos
✅ Una prueba = un concepto
✅ Patrón AAA

🎯 Próximo Paso​

¡Hora de la práctica! Descubramos JUnit 5 en detalle.

➡️ Capítulo 2: Pruebas Unitarias con JUnit 5


⬅️ Volver al Módulo 9