JUnit

🧩 Fundamentos de JUnit

JUnit es el framework estándar de testing para Java, utilizado para escribir y ejecutar pruebas unitarias automatizadas.
Su principal objetivo es validar el comportamiento esperado de métodos y clases, asegurando que los cambios no rompan funcionalidades existentes.

Características clave

  • Compatibilidad con Maven y Gradle
  • Anotaciones para definir el ciclo de vida del test (@BeforeAll, @AfterEach, etc.)
  • Soporte para Testing parametrizado
  • Integración con Mockito y AssertJ
  • Extensiones con JUnit 5 Jupiter API

⚙️ Estructura de un Test

Un test en JUnit sigue una estructura simple:

  1. Preparación (Arrange): configurar los datos o dependencias necesarias.
  2. Ejecución (Act): ejecutar el método a probar.
  3. Verificación (Assert): comprobar que los resultados sean los esperados.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculadoraTest {

	@Test
	void suma_debeRetornarResultadoCorrecto() {
		Calculadora calc = new Calculadora();
		int resultado = calc.sumar(2, 3);
		assertEquals(5, resultado);
	}
}

`

🧱 Ciclo de vida de los Tests

JUnit permite controlar la inicialización y limpieza de recursos antes y después de los tests.

@BeforeAll    // Ejecutado una vez antes de todos los tests
@BeforeEach   // Ejecutado antes de cada test
@AfterEach    // Ejecutado después de cada test
@AfterAll     // Ejecutado una vez después de todos los tests

Ejemplo:

@BeforeEach
void configurar() {
	// Configura datos o mocks antes de cada prueba
}

🧪 Tests Parametrizados

Permiten ejecutar el mismo test con múltiples valores de entrada.

@ParameterizedTest
@ValueSource(strings = {"madre", "padre", "hermano"})
void testLongitudCadenas(String palabra) {
	assertTrue(palabra.length() > 3);
}

Tipos de fuentes de parámetros

  • @ValueSource — valores simples (int, String, etc.)
  • @CsvSource — combina varios valores
  • @MethodSource — obtiene datos desde un método
  • @EnumSource — usa enums como datos de entrada

🧩 Assertions más usadas

  • assertEquals(expected, actual)
  • assertTrue(condition)
  • assertFalse(condition)
  • assertThrows(Exception.class, () -> { ... })
  • assertAll("grupo de asserts", () -> {...}, () -> {...})
assertAll("Validaciones múltiples",
	() -> assertEquals(4, calc.sumar(2, 2)),
	() -> assertThrows(ArithmeticException.class, () -> calc.dividir(10, 0))
);

🧰 Integración con Mockito

JUnit se complementa con Mockito para simular dependencias.

@ExtendWith(MockitoExtension.class)
class ServicioUsuarioTest {

	@Mock
	private RepositorioUsuario repo;

	@InjectMocks
	private ServicioUsuario servicio;

	@Test
	void debeGuardarUsuarioCorrectamente() {
		when(repo.save(any())).thenReturn(new Usuario("Ana"));
		Usuario u = servicio.crearUsuario("Ana");
		assertEquals("Ana", u.getNombre());
	}
}

🔄 Buenas prácticas

  • Nombrar los tests de forma descriptiva (en inglés o español).
  • Aislar cada prueba del entorno externo.
  • Evitar dependencias entre tests.
  • Usar CI/CD para ejecutar los tests automáticamente.
  • Medir la Cobertura de código con JaCoCo o SonarQube.
  • Organizar los tests por módulo o paquete.

⚡ Extensiones y complementos

  • @Tag: permite clasificar tests por categoría.
  • @Disabled: desactiva temporalmente un test.
  • @RepeatedTest: ejecuta un test varias veces.
  • @DisplayName: define un nombre legible para el test.
@DisplayName("Prueba repetida de conexión")
@RepeatedTest(3)
void testConexion() {
	assertTrue(servicio.conectar());
}

🧭 Integración con CI/CD

JUnit se ejecuta fácilmente en pipelines de GitHub Actions, Jenkins, o GitLab CI.
Los reportes XML generados pueden ser interpretados por herramientas de análisis continuo o dashboards personalizados.


📚 Recursos adicionales

  • Testing de APIs REST con JUnit y Spring Boot
  • Mocking avanzado con Mockito y JUnit
  • Patrones de testing en Java
  • Testing de integración con Testcontainers

JUnit - Conceptos Avanzados y Patrones de Testing

  • JUnit
  • Testing
  • Java
  • Spring Boot
  • Mockito
  • Arquitectura de tests

🧩 Testing de Integración con JUnit y Spring Boot

Los tests de integración validan la interacción entre varios componentes (servicios, controladores, repositorios, bases de datos).

Configuración básica

@SpringBootTest
@AutoConfigureMockMvc
class ControladorUsuarioIT {

	@Autowired
	private MockMvc mockMvc;

	@Test
	void debeRetornarListaUsuarios() throws Exception {
		mockMvc.perform(get("/usuarios"))
			.andExpect(status().isOk())
			.andExpect(jsonPath("$").isArray());
	}
}

`

Puntos clave

  • Usa un contexto real de Spring con dependencias cargadas.
  • Permite pruebas HTTP simuladas con MockMvc.
  • Integra fácilmente bases de datos en memoria como H2.

🧱 Testing de Bases de Datos con Testcontainers

Permite ejecutar bases de datos reales (PostgreSQL, MySQL, MongoDB) en contenedores Docker durante los tests.

@Testcontainers
@SpringBootTest
class RepositorioUsuarioIT {

	@Container
	static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
		.withDatabaseName("testdb")
		.withUsername("user")
		.withPassword("pass");

	@Autowired
	private RepositorioUsuario repo;

	@Test
	void debeGuardarYRecuperarUsuario() {
		Usuario u = new Usuario("Carlos");
		repo.save(u);
		assertEquals("Carlos", repo.findById(u.getId()).get().getNombre());
	}
}

Ventajas:

  • Pruebas realistas, sin mocks.
  • Reproduce entornos productivos.
  • Limpieza automática al finalizar los tests.

🧪 Testing de APIs REST con JUnit + RestAssured

import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Test
void debeRetornarUsuarios() {
	given()
		.when().get("/usuarios")
		.then()
		.statusCode(200)
		.body("size()", greaterThan(0));
}
  • Ideal para testing E2E de endpoints reales.
  • Permite validar cabeceras, códigos HTTP y payloads JSON.

⚙️ Testeo de Eventos, Jobs y Colas

JUnit también puede validar comportamientos asíncronos o basados en colas como Kafka, RabbitMQ o Spring Events.

@ExtendWith(MockitoExtension.class)
class EventoUsuarioTest {

	@Mock
	private ApplicationEventPublisher publisher;

	@InjectMocks
	private ServicioUsuario servicio;

	@Test
	void debePublicarEventoAlCrearUsuario() {
		servicio.crearUsuario("Ana");
		verify(publisher).publishEvent(any(UsuarioCreadoEvent.class));
	}
}

Puntos clave:

  • Validar la emisión de eventos.
  • Usar awaitility para esperar eventos asincrónicos.
  • Testear consumidores o handlers específicos.

🧠 Patrones de Testing en JUnit

1. Given-When-Then

Facilita la lectura de tests como escenarios de comportamiento.

// Given
Usuario u = new Usuario("Pedro");
// When
u.activar();
// Then
assertTrue(u.isActivo());

2. Object Mother

Centraliza la creación de objetos de prueba.

class UsuarioMother {
	static Usuario activo() {
		Usuario u = new Usuario("Activo");
		u.activar();
		return u;
	}
}

3. Fixture

Inicializa datos comunes a varios tests usando @BeforeEach.

4. Parameterized Behavior

Reduce duplicación en tests similares usando @MethodSource.


🧩 Extensiones de JUnit 5

JUnit 5 permite crear extensiones personalizadas (similar a middlewares).

public class TiempoDeEjecucionExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
	public void beforeTestExecution(ExtensionContext ctx) { start = System.currentTimeMillis(); }
	public void afterTestExecution(ExtensionContext ctx) {
		System.out.println(ctx.getDisplayName() + " ejecutado en " + (System.currentTimeMillis() - start) + " ms");
	}
}

Aplicación:

@ExtendWith(TiempoDeEjecucionExtension.class)
class MiTest {}

Permite:

  • Medir tiempos.
  • Inyectar dependencias personalizadas.
  • Aplicar configuraciones globales.

🧭 Testing de Rendimiento y Carga Ligera

Para microbenchmarks o validación de rendimiento básico:

@Test
void debeEjecutarseEnMenosDe200ms() {
	long start = System.currentTimeMillis();
	servicio.procesarDatos();
	assertTrue(System.currentTimeMillis() - start < 200);
}

También se puede integrar con:

  • JMH (Java Microbenchmark Harness)
  • Gatling para escenarios de carga HTTP
  • JUnit Performance Plugin

🔄 Pruebas Condicionales y Dinámicas

JUnit permite ejecutar tests según condiciones del entorno o generar tests dinámicos en tiempo de ejecución.

@EnabledOnOs(OS.WINDOWS)
@DisabledIfEnvironmentVariable(named = "CI", matches = "true")
@Test
void soloEnLocalWindows() { ... }

@TestFactory
Stream<DynamicTest> testGenerado() {
	return Stream.of("a", "b", "c")
		.map(letra -> dynamicTest("Prueba con " + letra,
			() -> assertTrue(letra.matches("[a-c]"))));
}

🧰 Integración con Arquitectura Limpia y DDD Domain-Driven Design

Los tests deben reflejar la arquitectura del dominio:

  • Tests de dominio puro sin dependencias externas.
  • Tests de infraestructura con adaptadores reales o mocks.
  • Tests de aplicación verificando flujos completos.

Organización sugerida:

src/test/java
│
├── dominio/
├── aplicacion/
└── infraestructura/

📚 Recursos complementarios

  • Mockito avanzado y testeo de excepciones en JUnit
  • Testing de integración con Docker y Spring Boot
  • JUnit 5 Extensions API
  • Patrones de testing en arquitectura hexagonal
  • Testing continuo con CI/CD y reportes JUnit XML
  • Performance Testing con JMH y JUnit

🧪 JUnit - Casos Reales de Testing en Proyectos Java

  • JUnit
  • Mockito
  • Spring Boot
  • Testcontainers
  • Testing de APIs
  • Arquitectura Hexagonal

🧩 Caso 1: Testing de un Servicio con Dependencias

Prueba típica de un servicio de dominio que depende de un repositorio o cliente externo.
Usamos @Mock y @InjectMocks para aislar la lógica.

@ExtendWith(MockitoExtension.class)
class ServicioPedidoTest {

	@Mock
	private RepositorioPedido repo;

	@InjectMocks
	private ServicioPedido servicio;

	@Test
	void debeCrearPedidoYGuardarEnRepositorio() {
		Pedido pedido = new Pedido("producto-123", 2);
		when(repo.save(any())).thenReturn(pedido);

		Pedido resultado = servicio.crearPedido("producto-123", 2);

		assertEquals("producto-123", resultado.getProductoId());
		verify(repo, times(1)).save(any(Pedido.class));
	}
}

`

Puntos clave

  • Se valida el flujo sin acceder a la base de datos.
  • verify() asegura que la interacción ocurrió como se esperaba.
  • Se controla completamente el entorno del test.

⚙️ Caso 2: Testing de Controladores REST con Spring Boot y MockMvc

Simula peticiones HTTP sin necesidad de servidor real.

@SpringBootTest
@AutoConfigureMockMvc
class ControladorProductoTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	void debeRetornarListaDeProductos() throws Exception {
		mockMvc.perform(get("/productos"))
			.andExpect(status().isOk())
			.andExpect(jsonPath("$.length()").value(greaterThan(0)));
	}
}

Ventajas

  • Evita levantar un servidor real.
  • Permite validar rutas, payloads y respuestas HTTP.
  • Integra con Jackson para serialización/deserialización JSON.

🧱 Caso 3: Testing con Testcontainers + PostgreSQL

Ejemplo de prueba de integración contra una base de datos real.

@Testcontainers
@SpringBootTest
class RepositorioProductoIT {

	@Container
	static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
		.withDatabaseName("productos_test")
		.withUsername("test")
		.withPassword("test");

	@Autowired
	private RepositorioProducto repo;

	@Test
	void debeGuardarYRecuperarProducto() {
		Producto p = new Producto("Taza cerámica", 12.5);
		repo.save(p);

		Optional<Producto> recuperado = repo.findByNombre("Taza cerámica");
		assertTrue(recuperado.isPresent());
		assertEquals(12.5, recuperado.get().getPrecio());
	}
}

Beneficios

  • Crea un entorno reproducible con datos reales.
  • No requiere configuración manual de base de datos.
  • Limpieza automática tras la ejecución.

📦 Caso 4: Testing de Publicación de Eventos

Simula el comportamiento de eventos de dominio o aplicación al ejecutarse una acción.

@ExtendWith(MockitoExtension.class)
class ServicioUsuarioTest {

	@Mock
	private ApplicationEventPublisher publisher;

	@InjectMocks
	private ServicioUsuario servicio;

	@Test
	void debePublicarEventoAlRegistrarUsuario() {
		servicio.registrarUsuario("Juan", "correo@ejemplo.com");
		verify(publisher).publishEvent(any(UsuarioRegistradoEvent.class));
	}
}

Notas

  • Ideal para sistemas basados en Event Driven Architecture.
  • Se testean los efectos colaterales del dominio sin ejecutar el listener real.

🧠 Caso 5: Testing de Validaciones y Excepciones

Verifica que el dominio arroje errores cuando se incumplen reglas.

@Test
void debeLanzarExcepcionSiElPrecioEsNegativo() {
	IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> {
		new Producto("Camisa", -10);
	});
	assertEquals("El precio no puede ser negativo", ex.getMessage());
}

Recomendaciones

  • Testear cada regla de negocio por separado.
  • Usar assertThrows para validar flujos de error.
  • Mantener mensajes de error consistentes.

🧩 Caso 6: Testing Parametrizado de Reglas de Negocio

Reduce la duplicación de tests al probar varias combinaciones.

@ParameterizedTest
@CsvSource({
	"100, true",
	"0, false",
	"-1, false"
})
void debeValidarMontoMinimo(double monto, boolean esperado) {
	assertEquals(esperado, ServicioPago.esMontoValido(monto));
}

Beneficios

  • Mayor cobertura con menos código.
  • Se documentan las entradas y salidas esperadas.
  • Facilita testear funciones puras o estáticas.

🌐 Caso 7: Testing de APIs Externas con WireMock

Simula respuestas HTTP de un servicio externo (por ejemplo, una API de pagos o clima).

@ExtendWith(WireMockExtension.class)
@WireMockTest(httpPort = 8081)
class ClienteApiClimaTest {

	private ClienteApiClima cliente = new ClienteApiClima("http://localhost:8081");

	@Test
	void debeRetornarTemperaturaSimulada() {
		stubFor(get(urlEqualTo("/clima?ciudad=Madrid"))
			.willReturn(aResponse()
				.withStatus(200)
				.withHeader("Content-Type", "application/json")
				.withBody("{\"temperatura\":25}")));

		double temp = cliente.obtenerTemperatura("Madrid");
		assertEquals(25, temp);
	}
}

Casos de uso comunes

  • Simular APIs de terceros.
  • Probar manejo de errores HTTP.
  • Validar reintentos y timeouts.

🔁 Caso 8: Testing Asíncrono con Awaitility

Verifica procesos que tardan en completarse o dependen de hilos.

import static org.awaitility.Awaitility.*;

@Test
void debeProcesarColaDePedidosEnMenosDe5Segundos() {
	servicio.enviarPedidosPendientes();

	await().atMost(5, TimeUnit.SECONDS)
		.until(() -> servicio.todosProcesados());
}

Ideal para

  • Jobs en segundo plano.
  • Procesamiento de colas.
  • Tests E2E controlados.

🧮 Caso 9: Testing de Performance Ligero

Evalúa tiempo máximo permitido de ejecución.

@Test
void debeEjecutarseEnMenosDe300ms() {
	long inicio = System.nanoTime();
	servicio.procesarLote(1000);
	long duracion = (System.nanoTime() - inicio) / 1_000_000;
	assertTrue(duracion < 300, "El procesamiento fue demasiado lento: " + duracion + " ms");
}

Mejores prácticas

  • Úsalo como indicador, no como métrica absoluta.
  • Evita tiempos muy ajustados que dependan del entorno.

🧭 Caso 10: Testing de Arquitectura con ArchUnit

Valida reglas de estructura en el código (por ejemplo, dependencias entre capas).

import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;

@AnalyzeClasses(packages = "com.miapp")
class ArquitecturaTest {

	@ArchTest
	static final ArchRule serviciosNoDebenDependerDeControladores =
		ArchRuleDefinition.noClasses()
			.that().resideInAPackage("..servicio..")
			.should().dependOnClassesThat().resideInAPackage("..controlador..");
}

Beneficio

  • Asegura adherencia a la arquitectura limpia o hexagonal.
  • Detecta dependencias circulares y malas prácticas.

✅ Resumen

Tipo de Test Herramienta Principal Objetivo
Unitario JUnit + Mockito Lógica aislada
Integración JUnit + Testcontainers Persistencia real
API REST MockMvc / RestAssured Validar endpoints
Eventos / Asíncronos Mockito / Awaitility Flujos paralelos
Externos WireMock Simular dependencias
Performance JUnit Medir tiempos básicos
Arquitectura ArchUnit Reglas estructurales

📚 Recursos complementarios

  • Testcontainers con Docker Compose y JUnit
  • WireMock para simular APIs externas
  • Awaitility - Testing Asíncrono en Java
  • ArchUnit - Validación de Arquitecturas
  • Buenas prácticas de testing en microservicios

🧩 Patrones Avanzados de Testing y Estrategias de Mantenimiento de Test Suites

  • JUnit
  • Testing
  • Arquitectura de Tests
  • Refactorización
  • CI/CD
  • Java

🧠 Objetivo

El propósito de esta nota es unificar los patrones avanzados de diseño de tests y las estrategias de mantenimiento que garantizan calidad, legibilidad y escalabilidad en suites de testing profesionales.
Aplicable a entornos con JUnit 5, Spring Boot, Mockito y arquitecturas DDD Domain-Driven Design o Hexagonal.


🧩 1. Patrón: Object Mother

Centraliza la creación de objetos complejos usados en múltiples tests, reduciendo duplicación.

public class UsuarioMother {

	public static Usuario activo() {
		Usuario u = new Usuario("Carlos", "carlos@correo.com");
		u.activar();
		return u;
	}

	public static Usuario inactivo() {
		return new Usuario("Ana", "ana@correo.com");
	}
}

`

Uso:

@Test
void debeEnviarCorreoAUsuarioActivo() {
	Usuario usuario = UsuarioMother.activo();
	servicio.enviarCorreo(usuario);
	assertTrue(usuario.tieneCorreoPendiente());
}

Ventajas

  • Reutilización de datos de prueba coherentes.
  • Centralización de constructores repetidos.
  • Fácil mantenimiento si cambian los modelos del dominio.

🧱 2. Patrón: Data Builder

Permite crear objetos de prueba de forma expresiva y configurable.

public class PedidoBuilder {
	private String producto = "Taza";
	private int cantidad = 1;
	private double precio = 9.99;

	public static PedidoBuilder unPedido() { return new PedidoBuilder(); }

	public PedidoBuilder conCantidad(int cantidad) {
		this.cantidad = cantidad;
		return this;
	}

	public PedidoBuilder conPrecio(double precio) {
		this.precio = precio;
		return this;
	}

	public Pedido construir() {
		return new Pedido(producto, cantidad, precio);
	}
}

Uso:

@Test
void debeCalcularTotalCorrecto() {
	Pedido pedido = PedidoBuilder.unPedido().conCantidad(3).conPrecio(5.0).construir();
	assertEquals(15.0, pedido.total());
}

Beneficios

  • Claridad semántica en los tests.
  • Independencia de constructores extensos.
  • Facilita agregar nuevos atributos sin romper tests.

🧪 3. Patrón: Test Fixture Reutilizable

Usar @BeforeEach o clases base de test para inicializar datos y mocks comunes.

abstract class BaseTest {
	protected ServicioUsuario servicio;
	protected RepositorioUsuario repo;

	@BeforeEach
	void setup() {
		repo = mock(RepositorioUsuario.class);
		servicio = new ServicioUsuario(repo);
	}
}

class ServicioUsuarioTest extends BaseTest {

	@Test
	void debeGuardarUsuario() {
		when(repo.save(any())).thenReturn(new Usuario("Juan"));
		assertEquals("Juan", servicio.crearUsuario("Juan").getNombre());
	}
}

Ventajas

  • Reutilización de configuración.
  • Menos repetición en suites grandes.
  • Facilita migración entre frameworks.

⚙️ 4. Patrón: Fakes, Stubs, Mocks y Spies

Tipo Propósito Ejemplo
Fake Implementación simplificada real. Fake DB en memoria
Stub Devuelve respuestas predefinidas. when(...).thenReturn(...)
Mock Verifica interacciones. verify(...)
Spy Observa llamadas reales. spy(new ServicioReal())

Ejemplo:

@Test
void debeGuardarUsuarioConFakeRepositorio() {
	RepositorioUsuarioFake repo = new RepositorioUsuarioFake();
	ServicioUsuario servicio = new ServicioUsuario(repo);

	servicio.crearUsuario("Ana");
	assertEquals(1, repo.count());
}

🔄 5. Patrón: Snapshot Testing en Java

Permite comparar salidas complejas con una versión “guardada” (snapshot). Se usa en serialización JSON o respuestas API.

Ejemplo con JSONAssert:

@Test
void debeCoincidirConSnapshotEsperado() throws Exception {
	String jsonActual = mapper.writeValueAsString(new Producto("Taza", 10.0));
	String jsonEsperado = Files.readString(Path.of("src/test/resources/snapshots/producto.json"));
	JSONAssert.assertEquals(jsonEsperado, jsonActual, false);
}

Ventajas:

  • Detecta regresiones visuales.
  • Facilita validar respuestas API.
  • Documenta el estado esperado de objetos complejos.

🧭 6. Patrón: Testing por Capas (Arquitectura Hexagonal)

Separar tests según el nivel de abstracción:

src/test/java
│
├── dominio/           → lógica pura
├── aplicacion/        → casos de uso y servicios
├── infraestructura/   → bases de datos, APIs, colas
└── presentacion/      → controladores o endpoints

Reglas

  • Los tests de dominio no deben usar Spring ni dependencias externas.
  • Los tests de infraestructura pueden usar Testcontainers.
  • Los tests de aplicación validan integración entre capas.

⚡ 7. Patrón: Given-When-Then (GWT)

Estructura narrativa para describir casos de uso con claridad.

@Test
void debeAplicarDescuentoEnPedidoPremium() {
	// Given
	Pedido pedido = PedidoBuilder.unPedido().conPrecio(100).conCantidad(1).construir();

	// When
	pedido.aplicarDescuento(10);

	// Then
	assertEquals(90, pedido.total());
}

Beneficio: mejora la legibilidad y la trazabilidad de los escenarios.


🧰 8. Estrategias de Mantenimiento de Suites de Tests

🔹 Agrupación lógica por contexto

Usa @Nested para agrupar tests relacionados.

class ServicioPagoTest {

	@Nested
	class Validaciones {
		@Test void debeRechazarPagoInvalido() { ... }
	}

	@Nested
	class Procesamiento {
		@Test void debeConfirmarPago() { ... }
	}
}

🔹 Nombres descriptivos

Usar @DisplayName o convenciones expresivas:

  • debeGenerarFacturaCuandoPedidoEsValido()
  • shouldThrowExceptionWhenUserNotFound()

🔹 Evitar dependencias externas

Usar mocks o fakes, no llamadas reales.

🔹 Minimizar fragilidad

Evita depender de datos de entorno, fechas, o aleatoriedad.

🔹 Separar datos de configuración

Usar archivos de recursos (.json, .yml, .csv) en /src/test/resources.


📦 9. Mantenimiento Continuo y Refactorización de Tests

  • Revisar tests obsoletos cada sprint.
  • Eliminar redundancias entre clases similares.
  • Centralizar la lógica de asserts repetitivos en helpers.
public class Asserts {
	public static void assertUsuarioActivo(Usuario u) {
		assertTrue(u.isActivo());
		assertNotNull(u.getFechaActivacion());
	}
}

Uso:

Asserts.assertUsuarioActivo(usuario);

🔁 10. Estrategias de Ejecución en CI/CD

  • Ejecutar tests unitarios en cada push (fast feedback).
  • Ejecutar tests de integración en pipelines nocturnos.
  • Generar reportes JUnit XML o HTML (con Allure o Surefire).
  • Definir umbrales de cobertura mínima con JaCoCo.
  • Integrar con SonarQube para análisis estático.

Ejemplo de pipeline:

# .github/workflows/tests.yml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v3
        with:
          java-version: '21'
      - run: mvn test
      - run: mvn jacoco:report

🧮 11. Patrón: Golden Master Testing

Para sistemas legacy sin tests previos:

  1. Captura la salida actual de funciones críticas.
  2. Usa esa salida como referencia.
  3. Compara con futuras ejecuciones para detectar cambios inesperados.

Ideal cuando el comportamiento debe preservarse sin alterar la implementación.


🧩 12. Estrategias para Grandes Suites

Estrategia Descripción Herramienta sugerida
Parallel Tests Ejecutar tests en hilos simultáneos. @Execution(CONCURRENT)
Tagging Ejecutar subconjuntos (@Tag("slow")). mvn test -Dgroups=slow
Reportes Visuales Reportes HTML interactivos. Allure, ExtentReports
Test Impact Analysis Solo ejecuta tests afectados. Integración con GitHub Actions o Jenkins
     

🧭 13. Anti-patrones comunes

  • Overmocking: exceso de mocks que rompen la lógica real.
  • Fragile tests: dependen de implementaciones internas.
  • Huge Fixtures: setups demasiado grandes o lentos.
  • Copy-paste testing: duplicar casos similares sin sentido.
  • No asserts: tests que no verifican nada relevante.

📚 Recursos complementarios

  • Arquitectura limpia en testing con JUnit
  • Data Builders y Object Mothers en práctica
  • Testing en entornos legacy con Golden Master
  • Allure Report y métricas de test en CI/CD
  • Refactorización de suites de test grandes
  • Testing maintainable codebases