Software Engineering

Testcontainers: Cómo escribir pruebas de integración fiables. Nuestro corto viaje lleno de optimismo

07 November 2022 | Por Luis Gurrieri

No más problemas con tus colaboradores externos

Testcontainers: Cómo escribir pruebas de integración fiables. Nuestro corto viaje lleno de optimismo

En la construcción de un producto de software, podemos encontrarnos con la necesidad de integrarnos con otros servicios o componentes externos, por ejemplo, bases de datos, message brokers, servidores de cache, etc.

Efectuar las pruebas de integración con esos componentes era una tarea compleja, ya que requería una gran inversión de tiempo para poder replicar entornos con características similares a los entornos productivos. En algunos casos, por cuestiones presupuestarias no se podía disponibilizar más de una instancia.

Con el surgimiento del paradigma de la “containerización” y Docker, este tipo de actividades se volvió menos compleja para los desarrolladores, ya que podían hacerlas con más autonomía.

Aun así el proceso de automatización de las pruebas de integración no estaba del todo completo al requerir alguna configuración manual o el uso de herramientas externas para poder efectuar cambios en la configuración y poder así controlar el ciclo de vida de los contendores de cada servicio.

Sumado a lo anterior, existen otras limitaciones que dificultan poder probar la aplicación en ciertos escenarios. Por ejemplo, supongamos que queremos efectuar un chaos test interrumpiendo la ejecución de uno o más contenedores, en ese caso, podríamos implementarlo en forma simple con un script de shell, pero esa solución no es tan portable y demanda algo de configuración manual dependiente de cada entorno donde se vayan a ejecutar las pruebas.

Este tipo de pruebas donde existe interacción entre los componentes del sistema bajo prueba y otros servicios externos durante la ejecución de las pruebas se denomina integrated test, porque el resultado de cada prueba depende del estado actual de cada sistema o componente se encuentre al momento de ejecutar las pruebas y, por tanto, no cumple con el principio de FIRST (Fast, Isolated, Repeatable, Self-validating, Thorough)

Varias de las limitaciones anteriores pueden ser solucionadas utilizando Testcontainers (https://www.testcontainers.org/), una solución estándar para múltiples lenguajes de programación que permite gestionar el ciclo de vida de los contenedores y la configuración de todos los servicios necesarios para ejecutar nuestras pruebas de integración. Todo esto es posible hacerlos a través de una API programática con soporte para JAVA, .NET, Javascript, Python y Go, logrando un desarrollo más consistente para los tests de integración.

Soporte en Springboot

Si estamos trabajando en un proyecto con Springboot, tiene soporte para Testcontainers e, incluso, tenemos la posibilidad de seleccionar la dependencia desde spring Initializr (https://start.spring.io) Con Maven, solamente tenemos que añadir la dependencia:

<dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.17.4</version> </dependency>

Y con gradle lo hacemos así:

testImplementation ‘org.testcontainers:testcontainers:1.17.4’

Teniendo docker instalado en nuestro equipo y con las dependencias incluidas, podemos empezar a trabajar con Testcontainers. Vamos con un ejemplo simple: necesitamos integrar nuestra aplicación con un web server ngnix con una página personalizada. Esta prueba de integración la podemos hacer usando el componente GenericContainer para representar ese servicio que nuestra aplicación va a usar en los tests.

a.jpg

Este patrón es aplicable a cada servicio que tenga un imagen docker disponible.

Vemos algunos métodos de la API del componente GenericContainer como withCopyFileToContainer que permite copiar y reemplazar un archivo existente en el filesystem del contenedor. El método withExposedPorts para publicar el puerto 80 del contenedor. En el caso de que necesitemos exponer más de un puerto, podemos efectuar la llamada del método pasando un valor entero para cada puerto a exponer de esta forma:

.withExposedPorts(80, 443, 8080)

De esta forma habremos expuesto los puertos TCP 80, 443 y 8080.

También, la API permite acceder a la información de logging del contenedor llamando al método getLog, con la posibilidad de redirigir la salida. Con respecto a la gestión de volúmenes podemos mapear un archivo o directorio del filesystem del host dentro del contenedor como un volumen usando el método withFileSystemBind o el método withClasspathResourceMapping para mapear recursos exitentes en el classpath del proyecto.

Testcontainers modules

Si bien el patrón anterior simplifica el proceso de control de un contenedor, Testcontainers disponibiliza varios módulos que lo facilitan aún más. Así, por ejemplo, podemos usar servicios populares como Kafka, RabbitMQ o Redis, desde base de datos hasta varios servicios cloud.

En un proyecto JAVA, solo tenemos que agregar la dependencia de cada módulo que se desee usar. Supongamos que estamos trabajando con una base de datos Postgresql, entonces agregamos la dependencia: <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <scope>test</scope> </dependency>

Ahora sí, podríamos implementar una test de integración con Postgresql:

b.jpg c.jpg

La anotación Testcontainers habilita una extensión de Junit5 que va a gestionar en forma automática el ciclo de vida de contenedor, con posibilidad de iniciarlo o detenerlo en cada método de prueba de la clase PostgreGenericIT.

Pero, podemos aprovechar aún mas la integración existente en Spring efectuando una configuración más eficiente que permita reutilizar el contenedor aplicando un enfoque basado en el patrón Singleton:

d.jpg

Gracias a la anotación @DynamicPropertySource, podemos inicializar las propiedades del datasource, considerando la configuración actual del contenedor, logrando así que la aplicación se pueda conectar a la base de datos. Lo más interesante de este patrón es que al poder reutilizar el contendor en todos los tests, vamos a tener una reducción significativa en el consumo de recursos.

CI/CD Integration

La posibilidad de ejecutar los tests en diferentes plataformas de CI/CD es posible porque Testcontainers puede ser integrado en Github, Gitlab, AWS Codebuild, Tekton, Bitbucket pipelines o Drone CI. Dependiendo de la plataforma con la que estemos trabajando el equipo de desarrollo de Testcontainers hace recomendaciones a considerar en los pipelines.

Resumen

Un escenario común con el que podemos encontrarnos en las arquitecturas modernas de software es la integración con otros componentes o sistemas. Poder probar esas integraciones era una tarea muy ardua, pero con el surgimiento de Testcontainers, se impulsaron todas las bondades de la containerización en el ámbito del testing al proveer una forma simple y versátil de usar los contenedores en las pruebas al poder automatizar su configuración y la gestión de su ciclo de vida.

About us Possumus

Somos una compañía enfocada en crear valor, que impulsa a las empresas a lograr su transformación digital con estrategia, conocimiento e innovación.

Nuestros procesos están acreditados con la certificación ISO 9001:2015 de calidad en desarrollo de software.

www.possumus.tech

POSSUMUS, Partner de Microsoft

Compartir:TwitterLinkedinWhatsApp
More options
FacebookTelegramMailCompartir

Notas relacionadas

Loading...
Loading...
Possumus
Possumus

Copyright © 2024 Possumus. All Rigths Reserved.

Join Us

Instagram.pngFacebook.pngLinkedIn.pngTwitter.png