Platica - Pruebas modernas con Java y Spring

abadongutierrez 22 views 35 slides Sep 10, 2025
Slide 1
Slide 1 of 35
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35

About This Presentation

Pruebas modernas con Java y Spring


Slide Content

Pruebas con Java y Spring Rafael Guti é rrez ( [email protected] ) Encora Septiembre 2025

¿Por qué probar el software? En general: El sistema cumple con los requerimientos (el qué) y especificaciones (el cómo). Encontrar errores antes de llegar a producción. Prevenir que nuevos cambios no rompan funcionalidad existente ( regresión ). Reducción de costos. ¿Cómo Software Developer? Confianza para realizar refactoring. Código bien probado es fácil de mantener. ¡Documentación viva! Colaboración de equipo Integración continua: Habilita el uso de flujos de despliegue automatizados confiables.

Pirámide de pruebas

Antipatrón de la pirámide: el cono de helado

JUnit

JUnit 5 - intro 1997 - Inventado durante un vuelo de Europa a EUA por Kent Beck y Erich Gamma Kent Beck - XP, Agile Manifesto Erich Gamma - uno de los autores del libro de patrones de dise ño JUnit 5 en 2017 Jars Jupiter (test API) Platform (correr las pruebas) Vintage (soporte al JUnit “viejo”) JUnit 6 muy pronto

JUnit 5 - ciclo de vida Instancia por método de prueba: ayuda a mantener las pruebas aisladas. @TestInstance(Lifecycle.PER_METHOD), @TestInstance(Lifecycle.PER_CLASS) Ciclo de vida @Test @BeforeEach @AfterEach @BeforeAll @AfterAll @DisplayName @Disabled @Order Demo: MyFirstTestExampleTest, LifecycleExampleTest

JUnit 5 - assertions y assumptions Assertions: Ayudan a “verificar” la ejecución de la prueba. Métodos estáticos de org.junit.jupiter.api.Assertions.* : assertEquals : verifica igualdad assertTrue / assertFalse : Verifica que la condición se cumpla o no. assertNull / assertNotNull : Verifica que la referencia sea o no nula. assertThrows : Verifica que se tire una excepción al ejecutar un método. assertAll : agrupa verificaciones assertTimeout : valida tiempos de ejecución etc. Assumptions: Usadas cuando no tiene sentido continuar con la ejecución si no hay condiciones adecuadas. Métodos estáticos de org.junit.jupiter.api.Assumptions.* : assumeTrue / assumeFalse assumeThat etc. Demo: TimeTranslatorTest

JUnit 5 - Pruebas parametrizadas Pruebas con la misma estructura donde solo varían los datos de entrada. @ParameterizedTest : a nivel método @ParameterizedClass : a nivel de clase (experimental) @ValueSource - valores fijos @EnumSource - enumeraciones @MethodSource - un método estático provee valores. @FieldSource - un atributo estático provee valores. @CvsSource , @CvsFileSource - formato CVS @ArgumentsSource - clase implementando ArgumentsProvider Mismo ciclo de vida que @Test. Demo: TimeTranslatorParameterizedTest

JUnit 5 - Pruebas dinámicas @TestFactory Similares a las pruebas parametrizadas Ciclo de vida distinto, solo un @BeforeEach, @AfterEach por todo el conjunto de pruebas. Útiles cuando las pruebas no pueden ser expresadas en tiempo de compilación y se necesita “crearlas” en tiempo de ejecución. Son fábrica de pruebas generadas en “runtime”. Demo: TimeTranslatorDynamicTest

JUnit - Pruebas anidadas @Nested Ofrecen más capacidades de expresar relaciones entre grupos de pruebas. Hacen uso de clases anidadas. Facilitan pensar en la estructura de la prueba como una jerarquía . “A something, when X and Y happen , and Z is present, A should be ...” Demo: ActivityControllerSpringBootTest vs ActivityControllerSpringBootNestedTest

Hamcrest y AssertJ La biblioteca de assertions de JUnit es buena, pero, IMHO, no suficiente. Las pruebas deben comunicar su intención claramente. Hamcrest: biblioteca de “matchers” que pueden ser combinados para expresar mejor la “ intención ” de los assertions. AssertJ: biblioteca de assertions “fluidas” Mejores mensajes de error Demo: BetterAssertionsDemoTest

Test Doubles y Mocking Frameworks

Dobles de pruebas (1) “Impostores” que ayudan a aislar el código que se quiere probar Más rápidos que usar una implementación real. Hacen la ejecución de la prueba determinista. Simulan condiciones especiales Exponen información oculta que ayuda a validar.

Dobles de pruebas (2) Test Stub - Dan respuestas predefinidas, útiles para comportamientos deterministas. Test Spy - “graban” información al ser llamados, utiles para Mock Object - pre-programados con “expectativas”. Tiran errores en caso de no recibir las llamadas que se esperan (mock frameworks) Fake Object - implementaciones que funcionan, pero toman “shortcuts” (ejemplo, una in-memory database) Dummy* - solo para “rellenar ”, aunque no son realmente usados. *Variación de Fake Object

Mock Frameworks Mockito , EasyMock, PowerMock, etc. Funcionalidades: Creaci ó n de “dobles de pruebas” (completos y parciales) Especifican comportamiento Control en los valores de retorno Control en el “lanzamiento” de excepciones Comparación “Matching” de argumentos Verificación de llamadas “Espiar” objetos, llamadas y argumentos

Mockito y Test Stubs Reemplaza objetos reales con objetos específicos de prueba. Ayuda a devolver “entradas indirectas” deseadas desde otros componentes al SUT (Sistema bajo prueba, System under test ). Responder : Devuelve “entradas indirectas” válidas. Saboteur : Devuelve “entradas indirectas” inválidas. Temporary Test Stub : usado cuando el componente del cual se depende aún no está listo (está en desarrollo). Entity Chain Snipping : como un Responder pero para reemplazar algún objeto complejo de crear que usa el SUT. Hard-Coded Test Stub Configurable Test Stub Procedural Test Stub Demo: MockitoTestStubDemoTest

Mockito y Test Spies Ayuda a verificar el “comportamiento ”. Ayuda a capturar las “salidas indirectas” del SUT a otros componentes, para verificarlos después . Retrieval Interface : un test double que expone métodos para obtener la información “grabada ”. Self Shunt : Cuando la misma clase de prueba se instala como el doble de pruebas espía. Inner Test Double : Se crea una clase interna anónima como doble de prueba espía. Indirect Output Registry Ver: MockitoTestSpyDemoTest

Mockito y Mock Objects Muy similares a los stubs, pero podemos implementar verificación de “comportamiento ”. Se reemplaza el objeto real con un doble de pruebas que se puede verificar que fue usado correctamente por el SUT . Diferencia con los stubs: la verificación final. Demo: MockitoMockObjectDemoTest

Mockito y Fake Objects Ayudan a probar nuestro código cuando los componentes de los que se depende no existen aún. Poder probar nuestro código con componentes más “ligeros” y evitar pruebas lentas. Fake Database In-Memory Database Fake Web Service Fake Service Layer Demo: ejemplos anteriores

Spring Boot

¡Nada ! No necesitas levantar el contexto de Spring. Inyección de dependencias facilita usar dobles de prueba

Soporte a pruebas de Spring El framework de Spring provee soporte de “primera clase” a las pruebas de integración con el módulo spring-test. El soporte a pruebas de Spring Boot está en los módulos spring-boot-test y spring-boot-test-autoconfigure. Starter: spring-boot-startet-test Incluye bibliotecas como: JUnit 5, Spring Test, AssertJ, Hamcrest, Mockito, JSONassert, JsonPath, Awaitility. @SpringBootTest crea un ApplicationContext y/o WebServerApplicationContext Pruebas de “capas” con @*Test @TestConfiguration @ActiveProfiles @TestPropertySource

@ExtendWith(MockitoExtension.class), @Mock, @InjectMocks Ayuda a probar la capa de servicios. Mocks para la capa de repositorios y otros servicios. @Mock e @InjectMocks : soporte de Mockito a mocks usando anotaciones para JUnit. Demo: ActivityServiceTest

@JsonTest Ayuda a probar la serialización y deserialización de objetos. Autoconfigura el soporte a JSON mappers de acuerdo a las dependencias. Jackson (el más común) Gson Jsonb Se incluye soporte basado en AssertJ para JSONAssert y JsonPath para validar JSON. JSONAssert: verifica JSON como si fueran String JsonPath: navegar un JSON, hacer consultas a JSON Se puede usar Hamcrest. Demo: ActivityDTOJsonTest

@WebMvcTest Ayuda a probar la capa de controllers. Autoconfigura Spring MVC Escanea solamente @Controller, @ControllerAdvice, @JsonComponent, etc. @Component y @ConfigurationProperties no son escaneados. Pensado para probar un solo controller por clase de prueba. Demo: ActivityControllerWebMvcTest

@DataJpaTest Escanea @Entity clases Configura repositorios Spring Data JPA Si una base de datos embebida está en el classpath, la usa. IMHO, yo prefiero Testcontainers y usar base de datos real. Demo: ActivityRepositoryH2DataJpaTest

Es importante poder ejecutar pruebas de integración sin requerir del despliegue de la aplicación en servidor u otro tipo de infraestructura que el sistema use. Puede levantar un servidor, pero por defecto no lo hace. Atributo webEnvironment: MOCK ( predeterminado ), RANDOM_PORT, DEFINED_PORT, NONE @WebTestClient : Usa WebClient internamente y usa una API fluida para verificar respuestas. @TestRestTemplate : alternativa al RestTemplate para pruebas integrales Demo: ActivityServiceIntegrationTest Demo: ActivityControllerSpringBootTest @SpringBootTest

Testcontainers Servicios corriendo dentro de contenedores docker. Integrado con JUnit, permite correr un contenedor antes de correr las pruebas. Útil para pruebas integrales a servicios backend reales como Postgres, MySQL, MongoDB, Cassandra, etc. Contenedores administrados por Spring como @Bean Contenedores administrados por Testcontainers usando las extensiones de JUnit: @Testcontainers , @Container , @DynamicPropertySource Demo: ActivityRepositoryWithTestContainerDataJpaTest Demo: ActivityServiceIntegrationTest

Otras anotaciones @WebFluxTest @GraphQlTest @JdbcTest @JooqTest @DataJdbcTest @DataCassandraTest @DataCouchbaseTest @DataElasticsearchTest @DataMongoTest @DataNeo4jTest @DataRedisTest @DataLdapTest @RestClientTest @WebServiceClientTest etc.

Conclusion

¿Qué probar? Prueba comportamiento, no implementación. Prueba la interfaz pública de los componentes. Prueba casos “extremos” (edge cases) y casos de error. Prueba lógica de negocio. Prueba puntos de integración entre componentes. Prueba flujos de negocio. ¿Ocurrió un bug? Reproduce el error con una prueba y solo después arregla el issue. Práctica ágil de eXtreme Programming y Test Driven Development. Red - Green - Refactor . Usa la cobertura de código para encontrar flujos de negocio no probados.

Buenas prácticas Patrón AAA (Arrange, Act, Assert) para mantener las pruebas organizadas y consistentes. Probar una sola cosa por método (conceptualmente hablando). Cuida tu código de prueba como el código de producción. DRY, KISS Asegúrate de que la prueba falla. Evita aserciones primitivas. Puedes incluso crear tu “biblioteca” de aserciones muy ligada al lenguaje de dominio. Código claro, que las pruebas comuniquen bien su “intención”. Crea “test fixtures” reutilizables y fáciles de modificar para casos nuevos. Patrón Builder/Factories

“I'm not a great programmer; I'm just a good programmer with great habits.” ― Kent Beck

Referencias https://martinfowler.com/bliki/TestPyramid.html https://www.james-willett.com/the-evolution-of-the-testing-pyramid/ https://docs.junit.org/current/user-guide/#overview https://martinfowler.com/bliki/TestDouble.html https://martinfowler.com/articles/mocksArentStubs.html https://jsonassert.skyscreamer.org/ https://github.com/json-path/JsonPath https://hamcrest.org/JavaHamcrest/ https://assertj.github.io/doc/ https://www.baeldung.com/hamcrest-collections-arrays Libro: xUnit Test Patterns: Refactoring Test Code https://docs.spring.io/spring-framework/reference/testing.html https://docs.spring.io/spring-boot/reference/testing/index.html https://java.testcontainers.org/ https://java.testcontainers.org/test_framework_integration/junit_5/ https://martinfowler.com/bliki/TestCoverage.html