Si echamos un ojo a la serie de artículos publicados en el blog de Paradigma, sobre las soluciones de streaming dentro del ecosistema Kafka, vemos que se hizo foco en las distintas abstracciones de Kafka Streams y ksqlDB desde distintos aspectos, apoyándose en la implementación de un caso de uso específico para verificar el funcionamiento de cada tecnología.

En esta ocasión, vamos a completar el contenido que vimos en anteriores posts poniendo foco en cómo podemos implementar casos de prueba en nuestros procesos de streaming en Kafka para verificar y asegurar su correcto funcionamiento.

Para ello, vamos a usar como base las distintas implementaciones del caso de uso desarrollado en los artículos de la serie para cada una de las tecnologías: Kafka Streams DSL, Kafka Streams Processor API y ksqlDB.

¿Cómo asegurar la calidad de nuestra solución?

En primer lugar, hay que tener en cuenta que, cuando trabajamos con casos de uso de naturaleza asíncrona y orientados a un flujo de información que se debe analizar de forma continua, realizar pruebas sobre ese tipo de procesos puede ser complejo o tedioso.

Afortunadamente, existen herramientas que nos permiten implementar nuestros casos de prueba de forma sencilla. Estas herramientas son distintas para cada tecnología, por lo que vamos a diferenciar cada una de ellas.

Testing en Kafka Streams DSL y Kafka Streams Processor API

Para soluciones construidas con Kafka Streams (independientemente de su nivel de abstracción), vamos a mostrar distintas maneras de implementar casos de prueba que nos permitan asegurar la calidad de nuestro desarrollo, tanto a través de tests unitarios como a través de test de integración o end-to-end.

Test unitarios

Dentro del ecosistema de Kafka Streams, disponemos de una librería de utilidades de tests muy interesante que nos facilita bastante la tarea de implementar tests unitarios.

Librería de utilidades de tests.

Esta librería nos da acceso a TopologyTestDriver, que facilita la escritura de pruebas para verificar el comportamiento de las topologías definidas en nuestro proceso. Una parte interesante es que nos permite probar todo tipo de topologías, desde las simples que tienen un solo procesador, hasta las muy complejas que tienen varias fuentes, procesadores, receptores o subtopologías.

Podemos probar todo tipo de topologías.

De esta manera podemos escribir casos de prueba de forma fácil, ya que nos proporciona las capacidades necesarias para simular el entorno en el que se debe ejecutar nuestra topología sin necesidad de provisionar una instancia de Kafka. Entre estas capacidades, destacamos la posibilidad de simular los topics de entrada y salida que manejan las topologías, y la de proporcionar métodos para mandar a los topics el flujo de datos que necesitemos para nuestros tests, para poder leer los topics de salida y para comprobar los resultados.

También dispone de métodos para manejar el tiempo de reloj pudiendo, de esta forma, simular escenarios en los que se controlan espacios temporales para el procesado de información, como en el caso de uso implementado en los artículos de la serie ya mencionados.

Otro dato a tener en cuenta es que la ejecución de estos tests es muy ligera y rápida porque, como hemos indicado, no necesita una instancia de Kafka levantada.

Dicho esto, ¿cómo podemos implementar nuestros casos de prueba para nuestro código?

La manera para poder implementar este tipo de casos de prueba es similar a lo que haríamos con librerías como JUnit. Una vez hemos añadido la librería de utilidades de test de Kafka Streams que hemos mencionado anteriormente, tendremos que preparar nuestros casos de prueba.

En primer lugar, debemos preparar e inicializar la topología sobre la que queremos ejecutar nuestros casos de prueba.

Debemos preparar e inicializar la topología.

A continuación, haremos uso de TopologyTestDriver para poder simular el entorno de nuestra topología para cada caso de prueba que queramos implementar.

TopologyTestDriver para nuestra topología de caso de prueba.

Para finalizar, simplemente tendremos que crear nuestro paquete de datos, que nos servirá para probar la topología y realizar las validaciones necesarias.

Podemos echar un vistazo a los casos de prueba que hemos definido para el caso de uso implementado en el siguiente repositorio.

Test de integración

Aunque TopologyTestDriver es una herramienta muy buena para generar casos de pruebas sencillos, tiene una limitación importante. A día de hoy, solo permite simular topics de una única partición. Además, no simula latencias, cacheos, etc. que sí se producen en una instancia real de Kafka. Entonces, ¿qué podemos hacer?

Para solventar estas carencias, necesitamos implementar otra serie de casos de prueba más pesados, que sí se ejecuten sobre una instancia de Kafka.

Existen diferentes alternativas, como usar un Kafka embebido o utilizar Testcontainers. En este artículo vamos a hablar sobre esta última, ya que nos parece la más interesante de implementar, dado que nos apoyamos en toda la potencia del uso de contenedores y nos aproximamos todo lo posible al caso de prueba del entorno real.

En el blog de Paradigma podemos encontrar varios artículos sobre Testcontainers, que pueden servir de ayuda para entender qué se busca con esta tecnología.

Para nuestro caso de uso, poder usar Testcontainers para implementar los casos de pruebas resulta muy sencillo, ya que simplemente tenemos que añadir las dependencias necesarias a nuestro proyecto.

Usar Testcontainers para implementar los casos de pruebas resulta muy sencillo.

A partir de aquí, ya podemos empezar a implementar nuestros casos de pruebas.

En primer lugar, vamos a etiquetar la clase (o clases) de pruebas para indicar qué va a utilizar Testcontainers.

Etiquetar la clase (o clases) de pruebas para indicar qué va a utilizar Testcontainers.

Por otro lado, tenemos que indicar qué contenedor vamos a usar en la ejecución de nuestras pruebas.

Contenedor que vamos a usar en la ejecución de nuestras pruebas.

Una vez que hemos configurado el contenedor que nos proporcionará la instancia de Kafka (y a diferencia de TopologyTestDriver, que nos facilita métodos para publicar y consumir eventos), con Testcontainers tendremos que crear nuestros consumidores y productores a mano y, además, gestionar el envío de los mensajes de forma manual.

Para ver completamente los casos de prueba de integración implementados para este caso de uso, podéis visitar el siguiente repositorio.

De esta manera, ya podríamos implementar un gran abanico de pruebas que permitan consolidar nuestros procesos realizados con Kafka Streams DSL y Processor API.

Testing en ksqlDB

Como hemos visto anteriormente, en soluciones implementadas con Kafka Stream DSL o Processor API resulta sencillo implementar casos de pruebas, ya que los marcos de trabajo nos ofrecen esta posibilidad para verificar que el código asociado a la topología diseñada funciona según se ha descrito. Esto no solo nos favorece a la hora de realizar pruebas locales, sino también nos permite integrarlo en cualquier pipeline de CI/CD para promocionarlo entre los distintos entornos.

Pero, ¿qué pasa con ksqlDB?

ksqlDB ofrece una herramienta por línea de comandos, ksql-test-runner, que nos permite realizar pruebas para verificar que nuestras sentencias SQL asociadas a los procesos de streaming que queremos implementar se comportan correctamente.

En realidad, esta herramienta se centra en realizar únicamente tests unitarios de los scripts SQL generados para nuestro proceso. Es un enfoque totalmente válido, ya que toda la operativa se inicia y se crea en topics de Kafka. Si trabajamos bien los mensajes del topic de entrada y los de salida, estamos garantizando que no habrá problemas en ese proceso. Eso sí, la naturaleza de los casos de pruebas que puede implementar provoca que no sea necesaria ningún tipo de infraestructura, ya que no necesita ningún cluster de Kafka ni ksqlDB Server en ejecución.

Probando flujos con ksqlDB

El uso de esta herramienta es bastante sencillo, ya que simplemente hay que proporcionar una serie de ficheros y ejecutar la herramienta.

Para entender bien cómo funciona, vamos a ver cómo se implementaría un caso de prueba para nuestro caso de uso y qué pinta tienen los distintos ficheros que necesitamos.

En primer lugar, definimos el script con las sentencias SQL que definen el comportamiento de nuestro proceso de streaming, el cual deseamos verificar. En nuestro caso, break_up_test.sql.

Script con las sentencias SQL.

A partir de aquí se trabaja en el fichero de entrada, con los datos que queremos que procese el flujo que hemos implementado. En nuestro caso, break_up_test_input.json.

Fichero de entrada.

Y en el fichero de salida, con los datos esperados una vez se ejecute el proceso. En nuestro caso, break_up_test_output.json.

Fichero de salida.

Con todo ello, ya tenemos el caso de prueba preparado para ser ejecutado, por lo que simplemente hay que lanzar la utilidad ksql-test-runner indicando los ficheros que hemos construido en etapas anteriores.

Script con las sentencias SQL.

Para finalizar, solo nos queda verificar el comportamiento del caso de prueba ejecutado.

Como en otras herramientas de testing, los datos mostrados por pantalla son bastante intuitivos, por lo que rápidamente se verifica la prueba realizada.

Verificación de la prueba realizada.

La facilidad de esta herramienta, junto a la nula necesidad de requerimientos de infraestructura, hace que sea muy sencillo probar los distintos desarrollos que se implementen con ksqlDB.

Como en el caso de Kafka Streams, ya podríamos implementar un gran abanico de pruebas que permitan consolidar nuestros procesos realizados con ksqlDB.

¿Existen más maneras de implementar casos de prueba?

Antes de nada, comentar que estas tecnologías son proyectos vivos y con más usuarios según va pasando el tiempo, por lo que van a ir surgiendo distintas maneras de poder implementar casos de pruebas, ya sea mejorando lo que se tiene o integrándose con otras alternativas.

Durante el desarrollo del artículo, las herramientas descritas y los casos de prueba expuestos se basan en ofrecer alternativas sencillas para llevar a cabo una fase tan importante como son las pruebas, ya sea en una etapa u otra del ciclo de vida del proceso.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete