Pruebas automatizadas en Java: JUnit y Mockito
En la unidad anterior vimos la teoría del diseño de pruebas: tipos de pruebas, criterios de cobertura, casos de prueba de caja negra y caja blanca. Ahora damos el siguiente paso: cómo implementar esas pruebas en código Java usando las herramientas que utiliza la industria. Conocer la teoría sin las herramientas es como saber conducir en teoría sin haber tocado nunca un volante.
JUnit 5 es el framework de pruebas unitarias más usado en el ecosistema Java. Permite escribir pruebas como métodos Java normales, anotados con @Test, que el entorno de desarrollo puede ejecutar automáticamente y de forma repetible. Mockito es el complemento natural de JUnit: permite crear dobles de prueba — versiones simuladas de las dependencias de una clase, que responden de forma predecible durante las pruebas.
Juntos, JUnit y Mockito forman el núcleo de las pruebas unitarias en Java profesional. Son las herramientas que encontrarás en prácticamente cualquier proyecto Java empresarial, y su dominio es un requisito fundamental para trabajar en el sector.
Relación con la unidad anterior
JUnit 5: el framework de pruebas de Java
JUnit es el framework de pruebas unitarias estándar en Java, con más de 20 años de historia. Su versión actual, JUnit 5, fue lanzada en 2017 y supuso una renovación completa de la arquitectura. La versión 5 se compone de tres módulos: JUnit Platform (la base que permite ejecutar cualquier framework de pruebas), JUnit Jupiter (las nuevas anotaciones y el modelo de programación) y JUnit Vintage (compatibilidad con pruebas de JUnit 3 y 4 antiguas).
Cuando trabajas con Maven, añadir JUnit 5 al proyecto es tan sencillo como incluir una dependencia en elpom.xml. La versión actual se llamajunit-jupiter e incluye los tres submódulos:
1<dependencies>
2 <!-- JUnit 5 (Jupiter): el framework de pruebas -->
3 <dependency>
4 <groupId>org.junit.jupiter</groupId>
5 <artifactId>junit-jupiter</artifactId>
6 <version>5.10.1</version>
7 <scope>test</scope> <!-- solo se usa en pruebas, no en producción -->
8 </dependency>
9</dependencies>
10
11<build>
12 <plugins>
13 <!-- Maven Surefire 3.x es necesario para ejecutar JUnit 5 -->
14 <plugin>
15 <groupId>org.apache.maven.plugins</groupId>
16 <artifactId>maven-surefire-plugin</artifactId>
17 <version>3.2.2</version>
18 </plugin>
19 </plugins>
20</build>Estructura de una clase de pruebas
Por convención, las clases de prueba tienen el mismo nombre que la clase que prueban más el sufijoTest. Si la clase se llamaCalculadora, la clase de pruebas se llamaCalculadoraTest. Las clases de prueba van ensrc/test/java, en el mismo paquete que la clase que prueban (para poder acceder a miembros con visibilidad de paquete si es necesario).
1package com.miempresa.utilidades;
2
3import org.junit.jupiter.api.*;
4import static org.junit.jupiter.api.Assertions.*;
5
6class CalculadoraTest {
7
8 private Calculadora calc;
9
10 // Se ejecuta UNA VEZ antes de todos los tests de la clase
11 @BeforeAll
12 static void configurarEntorno() {
13 System.out.println("Iniciando suite de pruebas de Calculadora");
14 // Aquí se configura algo costoso que se comparte entre todos los tests
15 // (conexiones de BBDD, carga de datos estáticos, etc.)
16 }
17
18 // Se ejecuta ANTES de cada test individual
19 @BeforeEach
20 void crearInstancia() {
21 calc = new Calculadora(); // instancia fresca para cada test → independencia
22 }
23
24 // Se ejecuta DESPUÉS de cada test individual
25 @AfterEach
26 void limpiar() {
27 // liberar recursos creados en @BeforeEach si es necesario
28 }
29
30 // Se ejecuta UNA VEZ al finalizar todos los tests
31 @AfterAll
32 static void cerrarEntorno() {
33 System.out.println("Suite de pruebas de Calculadora finalizada");
34 }
35
36 @Test
37 @DisplayName("Suma de dos números positivos")
38 void testSumaPositivos() {
39 // Arrange — preparar los datos
40 int a = 5;
41 int b = 3;
42
43 // Act — ejecutar la operación
44 int resultado = calc.sumar(a, b);
45
46 // Assert — verificar el resultado
47 assertEquals(8, resultado, "5 + 3 debería ser 8");
48 }
49
50 @Test
51 @DisplayName("División entre cero lanza excepción")
52 void testDivisionEntreCero() {
53 assertThrows(
54 ArithmeticException.class,
55 () -> calc.dividir(10, 0),
56 "Dividir entre cero debe lanzar ArithmeticException"
57 );
58 }
59
60 @Test
61 @Disabled("Pendiente de implementar — issue #42")
62 void testRaizCuadradaNegativa() {
63 // este test se omite temporalmente
64 }
65}Anotaciones principales de JUnit 5
| Anotación | Cuándo se ejecuta | Uso típico |
|---|---|---|
| @Test | Marca un método como caso de prueba | En cada método que verifica un comportamiento concreto |
| @BeforeEach | Antes de cada método @Test | Crear la instancia de la clase bajo prueba (estado limpio) |
| @AfterEach | Después de cada método @Test | Liberar recursos, restablecer estado compartido |
| @BeforeAll | Una vez antes del primer @Test (método estático) | Inicializar recursos costosos compartidos por toda la clase |
| @AfterAll | Una vez al finalizar el último @Test (método estático) | Cerrar conexiones, limpiar recursos globales |
| @DisplayName | No es de ciclo de vida | Nombre legible del test para los informes (en vez del nombre del método) |
| @Disabled | No es de ciclo de vida | Desactivar temporalmente un test (siempre con un motivo en el valor) |
| @Nested | No es de ciclo de vida | Agrupar tests relacionados en clases internas para mejor organización |
| @Tag | No es de ciclo de vida | Etiquetar tests para ejecutar subconjuntos (rápidos, lentos, integración...) |
| @ParameterizedTest | Marca un método como prueba parametrizada | Ejecutar el mismo test con múltiples conjuntos de datos |
Aserciones: verificar el resultado
Las aserciones son el núcleo de cualquier test: son las comprobaciones que determinan si el test pasa o falla. JUnit 5 las agrupa en la clase estática Assertions. Cuando una aserción falla, el test se detiene inmediatamente y JUnit reporta el fallo con un mensaje que indica qué se esperaba y qué se obtuvo.
1import static org.junit.jupiter.api.Assertions.*;
2
3// ── Igualdad ─────────────────────────────────────────────────────────
4assertEquals(8, calc.sumar(5, 3));
5assertEquals(8, calc.sumar(5, 3), "Mensaje que se muestra si falla");
6assertNotEquals(0, calc.sumar(5, 3));
7
8// ── Valores booleanos ─────────────────────────────────────────────────
9assertTrue(gestor.hayStock("REF-001", 5));
10assertFalse(gestor.hayStock("REF-001", 1000));
11
12// ── Nulos ────────────────────────────────────────────────────────────
13assertNull(repo.buscarPorId(999));
14assertNotNull(repo.buscarPorId(1));
15
16// ── Excepciones ──────────────────────────────────────────────────────
17// assertThrows devuelve la excepción para poder inspeccionarla
18ArithmeticException ex = assertThrows(
19 ArithmeticException.class,
20 () -> calc.dividir(10, 0)
21);
22assertEquals("División entre cero no permitida", ex.getMessage());
23
24// ── Timeout ──────────────────────────────────────────────────────────
25// El test falla si tarda más de 500 milisegundos
26assertTimeout(Duration.ofMillis(500), () -> {
27 operacionQuePuedeSerLenta();
28});
29
30// ── Grupos de aserciones (todas se ejecutan aunque alguna falle) ──────
31assertAll("Datos del cliente",
32 () -> assertEquals("María", cliente.getNombre()),
33 () -> assertEquals("García", cliente.getApellido()),
34 () -> assertEquals("Madrid", cliente.getCiudad()),
35 () -> assertTrue(cliente.isActivo())
36);
37
38// ── Comparación aproximada para números decimales ────────────────────
39// No se debe usar assertEquals con doubles directamente por precisión
40assertEquals(3.14, calc.pi(), 0.001); // tercer argumento: delta de tolerancia
41// Con AssertJ (librería alternativa más expresiva):
42// assertThat(calc.pi()).isCloseTo(3.14, within(0.001))
43
44// ── Arrays y colecciones ─────────────────────────────────────────────
45assertArrayEquals(new int[]{1, 2, 3}, lista.toArray());
46assertEquals(List.of("a", "b", "c"), resultado);Pruebas parametrizadas
Una prueba parametrizada ejecuta el mismo test con múltiples conjuntos de datos. En vez de copiar y pegar el mismo test cambiando los valores, defines los datos en una fuente y JUnit ejecuta el test una vez por cada fila. Esto es especialmente útil para probar valores límite, casos especiales y equivalencias.
1import org.junit.jupiter.params.ParameterizedTest;
2import org.junit.jupiter.params.provider.*;
3
4class PreciosTest {
5
6 // ── @ValueSource: un parámetro simple ────────────────────────────
7 @ParameterizedTest
8 @ValueSource(ints = {1, 5, 100, 999})
9 @DisplayName("Cantidades positivas siempre aceptadas")
10 void cantidadPositiva_esValida(int cantidad) {
11 assertTrue(validador.esCantidadValida(cantidad));
12 }
13
14 // ── @CsvSource: múltiples parámetros por fila ─────────────────────
15 @ParameterizedTest(name = "{0} unidades a {1}€ = {2}€ total")
16 @CsvSource({
17 "1, 10.00, 10.00", // 1 unidad a 10€ → 10€
18 "3, 10.00, 30.00", // 3 unidades a 10€ → 30€
19 "5, 7.50, 37.50", // 5 unidades a 7.50€ → 37.50€
20 "10, 2.99, 29.90", // 10 unidades a 2.99€ → 29.90€
21 })
22 void calcularSubtotal(int cantidad, double precio, double esperado) {
23 double resultado = calculadora.calcularSubtotal(cantidad, precio);
24 assertEquals(esperado, resultado, 0.01);
25 }
26
27 // ── @MethodSource: datos complejos desde un método ────────────────
28 @ParameterizedTest
29 @MethodSource("proveedorPedidos")
30 void procesarPedido_calculaCorrectamente(Pedido pedido, double totalEsperado) {
31 double total = gestor.calcularTotal(pedido);
32 assertEquals(totalEsperado, total, 0.01);
33 }
34
35 // Método estático que proporciona los datos
36 static Stream<Arguments> proveedorPedidos() {
37 return Stream.of(
38 Arguments.of(new Pedido("A001", 2, 15.00, false), 36.30), // sin VIP: 30 + 21% IVA
39 Arguments.of(new Pedido("B002", 1, 50.00, true), 50.935), // con VIP: 42.50 + 21% IVA
40 Arguments.of(new Pedido("C003", 10, 5.00, false), 60.50) // sin VIP: 50 + 21% IVA
41 );
42 }
43
44 // ── @EnumSource: todos los valores de un enum ──────────────────── ─
45 @ParameterizedTest
46 @EnumSource(MetodoPago.class)
47 void todosLosMétodosDePago_sonAceptados(MetodoPago metodo) {
48 assertTrue(pasarela.aceptaMetodo(metodo));
49 }
50}Mockito: dobles de prueba
Para hacer una prueba unitaria real, necesitas probar una clase en aislamiento, sin que su resultado dependa de una base de datos, de una API externa, del sistema de ficheros o de cualquier otra dependencia. Si la prueba conecta a una base de datos real, ya no es una prueba unitaria — es una prueba de integración, más lenta, más frágil y más difícil de mantener.
Los dobles de prueba resuelven este problema: son objetos que sustituyen a las dependencias reales durante las pruebas. Mockito es la librería estándar para crear dobles de prueba en Java. Permite crear versiones simuladas de cualquier interfaz o clase no final, definir qué devuelven sus métodos, y verificar que se han llamado correctamente.
Mockito distingue entre varios tipos de dobles. Un stub es un doble que devuelve respuestas predefinidas — cuando llamas al método buscarPorId(1) devuelve un objeto concreto. Un mock va más allá: también registra todas las llamadas que recibe, permitiéndote verificar al final del test que se llamó exactamente a los métodos correctos con los parámetros correctos. En la práctica cotidiana, se usa el término "mock" para ambos conceptos, aunque técnicamente son distintos.
1<dependency>
2 <groupId>org.mockito</groupId>
3 <artifactId>mockito-junit-jupiter</artifactId>
4 <version>5.7.0</version>
5 <scope>test</scope>
6</dependency>Creación y uso de mocks: ejemplo completo
Imagina que tienes un ServicioDeClientes que depende de unRepositorioClientes (que accede a la base de datos) y de unServicioEmail (que envía correos). Para probarServicioDeClientes sin base de datos ni correo, creamos mocks de esas dos dependencias:
1package com.miempresa.clientes;
2
3import org.junit.jupiter.api.*;
4import org.junit.jupiter.api.extension.ExtendWith;
5import org.mockito.*;
6import org.mockito.junit.jupiter.MockitoExtension;
7import static org.junit.jupiter.api.Assertions.*;
8import static org.mockito.Mockito.*;
9
10@ExtendWith(MockitoExtension.class) // integra Mockito con JUnit 5
11class ServicioDeClientesTest {
12
13 // @Mock crea un doble de prueba para estas dependencias
14 @Mock
15 private RepositorioClientes repositorio;
16
17 @Mock
18 private ServicioEmail email;
19
20 // @InjectMocks crea la clase bajo prueba e inyecta los mocks
21 @InjectMocks
22 private ServicioDeClientes servicio;
23
24 // ── Test: buscar cliente existente ────────────────────────────────
25 @Test
26 @DisplayName("Buscar cliente existente devuelve el cliente correcto")
27 void buscarCliente_existente_devuelveCliente() {
28 // Arrange: definir qué devuelve el mock cuando se le llama
29 Cliente clienteSimulado = new Cliente(1L, "Ana García", "ana@email.com");
30 when(repositorio.buscarPorId(1L)).thenReturn(Optional.of(clienteSimulado));
31
32 // Act
33 Cliente resultado = servicio.buscarPorId(1L);
34
35 // Assert
36 assertNotNull(resultado);
37 assertEquals("Ana García", resultado.getNombre());
38 assertEquals("ana@email.com", resultado.getEmail());
39
40 // Verificar que se llamó al repositorio exactamente una vez
41 verify(repositorio, times(1)).buscarPorId(1L);
42 // Verificar que NO se envió ningún email (no era necesario)
43 verifyNoInteractions(email);
44 }
45
46 // ── Test: cliente no encontrado ───────────────────────────────────
47 @Test
48 @DisplayName("Buscar cliente inexistente lanza ClienteNoEncontradoException")
49 void buscarCliente_noExistente_lanzaExcepcion() {
50 // Arrange: el repositorio devuelve vacío para el id 999
51 when(repositorio.buscarPorId(999L)).thenReturn(Optional.empty());
52
53 // Act & Assert
54 ClienteNoEncontradoException ex = assertThrows(
55 ClienteNoEncontradoException.class,
56 () -> servicio.buscarPorId(999L)
57 );
58 assertEquals("Cliente con id 999 no encontrado", ex.getMessage());
59 }
60
61 // ── Test: dar de alta a un nuevo cliente ─────────────────────────
62 @Test
63 @DisplayName("Alta de cliente nuevo guarda y envía email de bienvenida")
64 void altaCliente_nuevo_guardaYEnviaEmail() {
65 // Arrange
66 Cliente nuevoCliente = new Cliente(null, "Luis Pérez", "luis@email.com");
67 Cliente clienteGuardado = new Cliente(42L, "Luis Pérez", "luis@email.com");
68 when(repositorio.guardar(nuevoCliente)).thenReturn(clienteGuardado);
69
70 // Act
71 Cliente resultado = servicio.darDeAlta(nuevoCliente);
72
73 // Assert: el cliente se guardó correctamente
74 assertNotNull(resultado.getId());
75 assertEquals(42L, resultado.getId());
76
77 // Verificar que se envió el email de bienvenida con los datos correctos
78 verify(email, times(1)).enviarBienvenida("luis@email.com", "Luis Pérez");
79 // Verificar que se llamó exactamente al método guardar del repositorio
80 verify(repositorio, times(1)).guardar(nuevoCliente);
81 }
82
83 // ── Test: simular excepción del repositorio ──────────────────────
84 @Test
85 @DisplayName("Error del repositorio se propaga como ServicioException")
86 void altaCliente_errorRepositorio_lanzaServicioException() {
87 // Arrange: el repositorio lanza una excepción (BD caída, etc.)
88 when(repositorio.guardar(any(Cliente.class)))
89 .thenThrow(new RuntimeException("Error de conexión a BBDD"));
90
91 // Act & Assert
92 assertThrows(
93 ServicioException.class,
94 () -> servicio.darDeAlta(new Cliente(null, "Test", "test@email.com"))
95 );
96 // Si el repositorio falla, no debe intentarse enviar el email
97 verifyNoInteractions(email);
98 }
99}Operaciones esenciales de Mockito
1// ── Crear mocks manualmente (sin anotaciones) ────────────────────────
2RepositorioClientes repo = mock(RepositorioClientes.class);
3ServicioEmail email = mock(ServicioEmail.class);
4
5// ── Definir comportamiento (stubbing) ─────────────────────────────────
6// Devolver un valor concreto
7when(repo.buscarPorId(1L)).thenReturn(Optional.of(cliente));
8
9// Devolver distintos valores en llamadas sucesivas
10when(repo.generarId())
11 .thenReturn(100L) // primera llamada → 100
12 .thenReturn(101L) // segunda llamada → 101
13 .thenReturn(102L); // tercera y siguientes → 102
14
15// Lanzar una excepción
16when(repo.guardar(any())).thenThrow(new RuntimeException("BD caída"));
17
18// Para métodos void: doThrow o doNothing
19doThrow(new IOException("Fallo de red")).when(email).enviar(anyString());
20doNothing().when(email).enviarBienvenida(anyString(), anyString());
21
22// ── Matchers de argumentos ────────────────────────────────────────────
23// any() acepta cualquier argumento del tipo
24when(repo.buscarPorId(anyLong())).thenReturn(Optional.empty());
25// eq() acepta exactamente ese valor
26when(repo.buscarPorNombre(eq("María"))).thenReturn(List.of(maria));
27// Otros: anyString(), anyInt(), anyList(), argThat(predicado)...
28
29// ── Verificar interacciones ───────────────────────────────────────────
30verify(repo, times(1)).guardar(nuevoCliente); // llamado exactamente 1 vez
31verify(repo, never()).eliminar(anyLong()); // nunca llamado
32verify(repo, atLeast(2)).buscarPorId(anyLong()); // llamado al menos 2 veces
33verify(repo, atMost(3)).buscarPorId(anyLong()); // llamado como máximo 3 veces
34
35// Verificar que un mock no recibió ninguna llamada
36verifyNoInteractions(email);
37// Verificar que no hubo más llamadas de las verificadas explícitamente
38verifyNoMoreInteractions(repo);
39
40// ── Capturar argumentos para inspeccionarlos ──────────────────────────
41ArgumentCaptor<Cliente> captor = ArgumentCaptor.forClass(Cliente.class);
42verify(repo).guardar(captor.capture());
43Cliente clienteGuardado = captor.getValue();
44assertEquals("María", clienteGuardado.getNombre());Cuándo NO usar mocks
Spy: doble de prueba parcial
Un spy es un doble de prueba que envuelve un objeto real. Por defecto, llama a los métodos reales del objeto envuelto, pero permite stubbing selectivo de métodos específicos. Es útil cuando necesitas que la mayoría de la lógica funcione de verdad, pero quieres controlar algún método concreto (por ejemplo, el método que consulta la hora actual, para que siempre devuelva una fecha fija).
1// Crear un spy sobre una instancia real
2GestorPedidos gestorReal = new GestorPedidos(repositorio);
3GestorPedidos spyGestor = spy(gestorReal);
4
5// La mayoría de métodos funcionan normalmente (llaman al código real)
6// Solo el que define el timestamp se stubbea:
7doReturn(LocalDate.of(2024, 1, 15)).when(spyGestor).getFechaActual();
8
9// Ahora el spy usa la fecha fija para la lógica que depende de la fecha
10Pedido pedido = spyGestor.crearPedido("REF-001", 3);
11assertEquals(LocalDate.of(2024, 1, 15), pedido.getFecha());Cobertura de código y herramientas del IDE
La cobertura de código (code coverage) mide qué porcentaje del código fuente es ejecutado durante las pruebas. Es una métrica útil para identificar partes del código que no tienen ninguna prueba, pero no debe confundirse con una garantía de calidad: el 100% de cobertura no implica que el código esté libre de errores, solo que las pruebas tocan cada línea. La calidad de las aserciones importa tanto o más que la cobertura.
JaCoCo (Java Code Coverage) es la herramienta estándar de cobertura en proyectos Java con Maven. Se integra como plugin y genera informes HTML detallados que muestran, línea a línea, qué código se ha ejecutado (verde), cuál no (rojo) y qué ramas de decisión (if/else, switch) se han tomado.
1<build>
2 <plugins>
3 <plugin>
4 <groupId>org.jacoco</groupId>
5 <artifactId>jacoco-maven-plugin</artifactId>
6 <version>0.8.11</version>
7 <executions>
8 <!-- Preparar el agente de instrumentación antes de los tests -->
9 <execution>
10 <id>prepare-agent</id>
11 <goals><goal>prepare-agent</goal></goals>
12 </execution>
13 <!-- Generar el informe HTML después de ejecutar los tests -->
14 <execution>
15 <id>report</id>
16 <phase>test</phase>
17 <goals><goal>report</goal></goals>
18 </execution>
19 <!-- Verificar umbrales mínimos de cobertura -->
20 <execution>
21 <id>check</id>
22 <goals><goal>check</goal></goals>
23 <configuration>
24 <rules>
25 <rule>
26 <element>BUNDLE</element>
27 <limits>
28 <!-- El build falla si la cobertura de líneas es < 70% -->
29 <limit>
30 <counter>LINE</counter>
31 <value>COVEREDRATIO</value>
32 <minimum>0.70</minimum>
33 </limit>
34 </limits>
35 </rule>
36 </rules>
37 </configuration>
38 </execution>
39 </executions>
40 </plugin>
41 </plugins>
42</build>1# Ejecutar pruebas y generar informe de cobertura
2mvn test
3
4# El informe se genera en:
5# target/site/jacoco/index.html
6# Abrirlo en el navegador para ver la cobertura visual
7
8# Ejecutar solo los tests (sin empaquetar)
9mvn test -pl modulo-concreto
10
11# Ejecutar un test específico
12mvn test -Dtest=CalculadoraTest
13
14# Ejecutar todos los tests cuyo nombre contenga "Pedido"
15mvn test -Dtest="*Pedido*"Ejecutar pruebas desde el IDE
IntelliJ IDEA y Eclipse integran JUnit de forma nativa. Puedes ejecutar un test individual haciendo clic en el icono verde junto al método, ejecutar toda la clase desde el icono junto al nombre de la clase, o ejecutar todos los tests del proyecto con Run → Run All Tests. El panel de resultados muestra qué tests han pasado (verde), cuáles han fallado (rojo) y el mensaje de error concreto de cada fallo.
Atajos en IntelliJ IDEA
- Ctrl+Shift+F10 — ejecutar el test actual
- Shift+F10 — repetir la última ejecución
- Ctrl+Shift+F10 en clase — ejecutar toda la clase
- Ver cobertura: Run → Run with Coverage
Resultado de los tests
- 🟢 Verde: el test ha pasado
- 🔴 Rojo: el test ha fallado (aserción no cumplida)
- ⚠️ Amarillo: el test lanzó una excepción inesperada
- ⭕ Gris: el test fue ignorado (@Disabled)
Buenas prácticas
- Un test, una responsabilidad: prueba un solo comportamiento
- Independencia: cada test debe poder ejecutarse solo
- Rapidez: los tests unitarios deben ejecutarse en milisegundos
- Determinismo: mismo resultado siempre, sin aleatoriedad
Flujo recomendado: TDD con JUnit + Mockito
Resumen de la unidad
JUnit 5
- ✓@Test, @BeforeEach, @AfterEach, @BeforeAll, @AfterAll
- ✓assertEquals, assertThrows, assertAll, assertTimeout
- ✓@ParameterizedTest con @ValueSource, @CsvSource, @MethodSource
- ✓@Nested para agrupar tests relacionados
Mockito
- ✓@Mock, @InjectMocks, @ExtendWith(MockitoExtension.class)
- ✓when().thenReturn() / doThrow() para definir comportamiento
- ✓verify() para comprobar que se llamaron los métodos correctos
- ✓ArgumentCaptor para inspeccionar argumentos recibidos
Cobertura de código
- ✓JaCoCo: plugin Maven que mide qué código se ejecuta
- ✓Informe HTML línea a línea (verde = cubierto, rojo = no cubierto)
- ✓Cobertura de ramas: verifica que se prueban if/else y switch
- ✓Umbral mínimo: el build falla si la cobertura es insuficiente
Integración con el IDE
- ✓Ejecución individual de tests desde el editor
- ✓Panel de resultados con detalle de cada fallo
- ✓Visualización de cobertura integrada (líneas marcadas en verde/rojo)
- ✓Refactorización segura gracias a la suite de pruebas