Mountebank y sus impostores, aislando nuestras pruebas

Introducción

En el blog hemos hablado en varias ocasiones sobre cómo mockear las APIs, teniendo en cuenta que es una parte importante para aislar nuestras pruebas en las arquitecturas basadas en microservicios.

Si bien es cierto que haciendo uso de contract testing podemos aislar nuestras pruebas y comprobar el correcto funcionamiento de nuestros servicios y los clientes, en ocasiones podemos necesitar llegar más lejos.

Pongamos un caso real. Hace unos meses que con mi equipo trabajamos en un proyecto en el que era necesario comunicarse con un sistema tercero asíncrono.

A nivel de pruebas lógicamente no podíamos acoplarnos a ese servicio, por lo que decidimos simularlo de alguna forma (teniendo en cuenta las restricciones tecnológicas que teníamos), es más, otros equipos necesitaban de ese servicio para sus pruebas (negocio, financiero, performance).

Por este motivo nos decantamos por desarrollar un servicio interno que simulara la respuesta del servicio externo, de tal forma que todos los equipos involucrados en el proyecto podrían utilizarlo. Con contract testing, este caso no podría ser.

Y aquí, es donde entran los “test doubles” y mountebank, una herramienta que nos va a permitir generar test doubles multi-protocolo. Gracias a Alvaro Salazar que me recomendó darle un vistazo.

¿Qué son los test doubles?

De forma sencilla, podemos decir que un test double es una copia o imagen de la respuesta y el comportamiento de un servicio.

En el caso de mountebank disponemos de dos tipos de test doubles: mocks y stubs. Dependiendo del tipo de pruebas que queramos hacer usaremos uno, otro, o los dos.

Con los mocks podemos comprobar que la llamada se ha hecho confiando en la respuesta del servidor, mientras que los Stubs, en base a una request nos devolverán una response, es decir, definimos su contrato.

¿Cómo funciona Mountebank?

Al contrario de otras herramientas, uno de los puntos fuertes de mountebank es la sencillez de generar los test doubles.

mountebank

Mountebank levanta un servicio que se encuentra a la escucha de que el cliente le mande el contrato para crear el test double, o impostor como también lo llaman. Este creará el impostor con los datos de configuración que se detallan en el contrato (protocolo, endpoint, puerto, petición, respuesta, comportamiento etc.)

La aplicación ya podrá consumir del test double, en vez del servicio real en el tiempo de test, aislando así nuestras pruebas.

Una buena estrategia es, tal como dicen en la página de mountebank, que en la preparación del entorno de pruebas, las pruebas sean las encargadas de enviar los contratos a mountebank para generar los test doubles, y cuando finalicen, sean las encargadas de comunicar que se eliminen. De esta forma todo quedará en tiempo de pruebas.

Otra forma de generar los test doubles, es mediante el uso del proxy que nos provee el servicio de mountebank. Mediante el proxy, mountebank capturará las llamadas reales que se hacen al servicio externo y creará los stubs en base a las peticiones y las respuestas que ha capturado. Lo veremos más adelante en otro post.

Un ejemplo sencillo de aislamiento

En este caso he preparado un proyecto muy muy sencillo, que podreis ver en github. En el proyecto vamos a probar la clase BookGateway, que es la encargada de consumir un servicio externo.

En este punto vemos que si nuestra intención es aislar nuestras pruebas de integración, vamos a tener que hacer un stub de ese servicio. Teniendo en cuenta que disponemos de la información, vamos a desarrollar el contrato:  

La idea es que el stub se gestione en el tiempo de pruebas, es decir, que cuando se lancen las pruebas se genere el stub, y cuando se terminen, se destruya. Para ello, vamos a  hacer uso de un cliente java de mountebank, javabank:

En el test, en el método @beforeAll vamos a crear el impostor cargandolo desde el fichero de configuración para que cada vez que se ejecute cree el impostor del servicio de book.

En el método @afterAll eliminamos todos los impostores, de tal forma que el entorno quede limpio para las siguientes ejecuciones.

El test es muy sencillo, simplemente llamamos al método getBook del gateway, el cual hará la petición al impostor en vez de al servicio externo.

Ahora solo nos queda instalar el servicio de mountebank y levantarlo, lo podéis ver en el siguiente enlace, y ejecutar las pruebas.

console

test results

El ejemplo que os he presentado es muy sencillo, con una configuración muy básica, pero mountebank nos permite generar stubs y mocks mucho más complejos, con diferentes requests y responses, así como definir diferentes comportamientos. En su documentación podéis verlo con mucha más profundidad.

Conclusiones

El disponer de test doubles (ya sea con mountebank u otra herramienta) puede ser un arma muy interesante en la batalla del testing de microservicios.

En mi opinión es una utilidad complementaria a contract testing y al uso de por ejemplo testcontainer como estrategia para llegar a tener unas pruebas determinísticas.

Uno de los beneficios de utilizar un servicio de test doubles, es que podemos utilizarlos por más de una aplicación y por más de un objetivo. Podemos tener el servicio de mountebank levantado y que diferentes pruebas consuman sus stubs. Bien es cierto que esto no es muy recomendable ya que perdemos ciertos beneficios del aislamiento ya que los compartimos, pero dependiendo del caso, tendremos la opción.

El beneficio claro de Mountebank es que es multiprotocolo, por lo que no solo nos va a proveer stubs o mocks HTTP, si no que, en el caso de querer mockear un servicio de correo SMTP o un servicio TCP también podremos. En nuestro caso hemos utilizado el cliente java, pero aquí tenéis los diferentes clientes para otros lenguajes.

Todavía me queda por investigar, es más, creo que es una herramienta que en el caso de mi equipo puede sernos de utilidad, así que os iré informando!

 

 

 

Pruebas aisladas e integradas ¿Qué son?

Introducción

Leyendo sobre estrategias de testing de microservicios en entornos PaaS, me he encontrado con un artículo sobre una charla bastante interesante que creo que tengo que compartir.

http://blog.thecodewhisperer.com/permalink/integrated-tests-are-a-scam

El post habla sobre el concepto de las pruebas integradas (que no de integración) y según J.B Raisemberg porque él cree que son una farsa. Sin duda, no podéis dejar de verlo!

En este post, analizaré dos de los conceptos que se tratan, las pruebas aisladas (isolated tests) y las pruebas integradas (integrated tests).

Qué son las pruebas integradas?

J.B Raisemberg define las pruebas integradas como:

“I use the term integrated test to mean any test whose result (pass or fail) depends on the correctness of the implementation of more than one piece of non-trivial behavior.”

tests results

Imaginemos que nuestro SUT (sistema bajo test) está compuesto por dos servicios que dependen de uno tercero. Lanzamos una suite de pruebas funcionales y como resultado obtenemos que varias de las pruebas han fallado.

 

Analizándolas vemos que son fallos debidos a la comunicación en el otro servicio.

Esto sería un ejemplo de que el resultado de nuestras pruebas funcionales dependen de otros componentes no triviales, por lo que estaríamos hablando de pruebas integradas.

En las pruebas de integración puede ocurrir lo mismo. En muchas ocasiones para poder validar una acción o una respuesta, es mayor la preparación de los componentes dependientes que la aserción del resultado. Bien es cierto, que esto puede mostrar un mal diseño de la prueba.

integrated pyramid

 

Si lo vemos bajo la pirámide tradicional del testing, las pruebas integradas serían todas las que no son unitarias, dado que tanto las de integración como las funcionales van a depender de componentes “externos” a la prueba en cuestión.

Por otro lado, las pruebas integradas aumentan considerablemente la complejidad del testing.

 

integrated paths

Supongamos que en nuestro SUT (sistema bajo test) tenemos tres componentes, con 5, 2 y 3 paths a probar. Si queremos probar toda la casuística combinatoria deberíamos tener 5*2*3 = 30 pruebas. En el caso de añadir un nuevo componente, por ejemplo con 2 paths llegaríamos a las 60 pruebas!

 

Ya no solo es que el coste de mantenimiento de la suite de pruebas sea alto, es que si uno de los componentes tiene un bug, afectará a todo el conjunto de pruebas y los falsos positivos se multiplican.

El coste de mantenimiento y la complejidad de las pruebas integradas como vemos puede llegar a ser exponencial O(n!).

complexity

Para los proyectos en los que estoy trabajando me estaba planteando hacer uso de machine learning, hacer un sistema para clasificar los errores bajo ciertos patrones, debido a que tenemos una cantidad importante de falsos positivos debido a entorno etc.

Pensando de forma objetiva, montar un proyecto de tal calibre para clasificar los fallos requiere de un gran esfuerzo, y en este caso no soluciona nada, solo enmascara los problemas: el diseño de las pruebas, el entorno y la estrategia de testing. Y todo porque disponemos de una suite compleja en la que la mayoría de las pruebas son integradas.

Y… ¿Las aisladas?

Cuando las pruebas sobre un componente no requieran de la integración con otro componente no trivial, hablaremos de pruebas aisladas.

isolated pyramid Es el caso de las pruebas unitarias, que se hacen de forma aislada para comprobar el correcto funcionamiento de nuestro código, simulando el comportamiento o la respuesta de ciertas partes u objetos del código.

 

 

 

 

lineal

El coste de mantenimiento y la complejidad de las pruebas aisladas es lineal O(n), ya el número de pruebas depende del número de paths que tengamos que probar por cada componente.

 

Si haríamos pruebas aisladas para el caso anterior, tendríamos en total 5 + 3 + 2 = 10 pruebas y no 30 como en el caso de las integradas.

Desacoplar y aislar las pruebas integradas

Las pruebas unitarias son aisladas por naturaleza (siempre que estén bien diseñadas), pero claro, en nuestro proyecto muy probablemente tengamos pruebas unitarias, de integración, funcionales etc.

¿Pero, cómo podemos convertir las pruebas integradas en aisladas?

Bueno pongámonos en caso anterior, en el que nuestro SUT está compuesto por dos servicios que se comunican entre sí y además dependen de uno tercero. Para poder probar los servicios de forma aislada debemos simular todas las respuestas esperadas.

integrated_aislated

En la imagen de abajo, vemos que hemos aislado las pruebas de los componentes mediante contract testing. De esta forma, en el lado del servicio validamos que este responde como tiene que responder a las diferentes peticiones, y que nuestro componente frente a una respuesta concreta reacciona como debería.

integrated_aislated

El ejemplo se ha mostrado con microservicios, pero en el caso de querer aislar los componentes de una aplicación monolítica se podría seguir una aproximación, al igual que para las pruebas de integración, como bien explica Raisemberg en su video.

pyramid

Finalmente, si echamos la mirada atrás a la pirámide tradicional del testing, vamos a ver que ahora el scope de las pruebas integradas se ha reducido. Si bien es cierto que podemos aislar ciertas pruebas funcionales, no podemos aislarlas todas.

 

 

 

 

 

Conclusiones

Creo que es importante pensar en un diseño de pruebas aisladas en la estrategia que sigamos en nuestros proyectos.

Puede que el diseño o la arquitectura de nuestro SUT parezca más compleja o que requiera de una mayor tecnicidad pero tenemos que valorarlo frente a las ventajas que nos ofrece el aislar nuestras pruebas:

  • Reducir el total de las pruebas.
  • Reducir los falsos positivos, mejorando así el análisis de los resultados.
  • Mejorar la mantenibilidad de la suite de pruebas.
  • Reducir la complejidad de las pruebas.
  • Reducir el tiempo de ejecución de las pruebas, debido a que las pruebas integradas dependen de comunicar con otros componentes (que a su vez pueden comunicar con otros) y por tanto de su tiempo de respuesta.

A parte de las ventajas que nos aporta el uso de contract testing, como podéis leer en un post en este mismo blog.

Pruebas de rendimiento y rastreo mediante Spring Cloud Sleuth y Zipkin

Introducción

Siguiendo por el hilo del post anterior en este artículo me gustaría tratar sobre el tracing de los servicios. Cuando hablamos de “tracear” los microservicios, estamos hablando de rastrear todas las llamadas y respuestas que se hacen entre los diferentes servicios de nuestro sistema.

Tener un control sobre cada llamada es muy importante ya que nos aporta una visibilidad en tiempo real del estado de cada petición y respuesta que se da, de tal forma que no perdemos ningún detalle de lo que ocurre (tipos de peticiones y respuestas, tiempos y errores) entre nuestros servicios.

En nuestro caso vamos a hacer las pruebas con zipkin como sistema de rastreo y para nuestros servicios vamos a hacer uso de Spring Cloud Sleuth que es la herramienta que nos aporta Spring Cloud para instrumentar sus servicios.

Podéis descargar el proyecto de pruebas como siempre desde mi GitHub.

He de decir que cuando desarrollé el proyecto de pruebas lo hice sobre la versión 1.3 de Sleuth, ahora se encuentran en su versión 2.0. Si queréis migrarlo os dejo el siguiente enlace.

No solo me gustaría centrarme en el uso de Zipkin o Sleuth, si no, que este post viene dado por un debate que tuve con mis compañeros de equipo (y que luego trasladé a Federico Toledo), sobre la mejor estrategia para probar el rendimiento de los servicios públicos e internos.

Entendamos públicos como aquellos que están abiertas al consumo exterior e internas como aquellas que solo son consumidos por otros servicios.

Mi opinión es que el mejor approach era realizar las pruebas de rendimiento sobre los servicios públicos, ya que estos internamente iban a llamar a los servicios internos, siempre y cuando tendríamos alguna forma clara de rastrear y obtener los datos de los tiempos y el rendimiento de los servicios internos.

Bien, ¿Y qué tal si usamos zipkin y sleuth y así vemos un ejemplo de esto mismo? Vamos a ver qué conclusiones sacamos.

De forma sencilla, como funciona Sleuth y Zipkin

Veamos un ejemplo del rastreo de una petición para ver y entender la información recogida. En la imagen de abajo vemos dos trazas. Las trazas están compuestas por spans (lo veremos ahora) y muestran la información del “recorrido” de la petición, es decir, lanzando una petición, porque servicios hemos pasado.

Traza zipkin
En este caso hemos lanzado una petición a purchase-service que internamente ha llamado a inventory y account-service. Si accedemos a la información de la traza, lo vemos con más detalle.

Lo que vamos a ver son los spans, es decir, la unidad de información que contiene los datos de una request/response, los tiempos etc. Vamos a tener un span por cada petición y respuesta que se haya dado dentro de la traza. Cada traza va a tener asociados una serie de spans, por lo que en nuestro caso los spans A, B y C estarán asociados a la traza T.

Zipkin spans

span info
Vamos a acceder al span de inventory-service para ver su información con más detalle:

Las anotaciones son los registros de los eventos de la petición. En este caso se puede ver los tiempos en los que el cliente ha realizado la petición (CS), esta se ha recibido (SR), se ha respondido (SS) y cuando el cliente ha obtenido la respuesta (CR).

Por otro lado, en la tabla inferior vemos información más detallada sobre la petición en cuestión. La key get-inventory-item y su valor se ha definido en la instrumentación del código, para aportar mayor información en el span.

Si hubiera algún error, veríamos los detalles del error en este punto:

zipkin error

Dado que creo que es un tema muy interesante, os recomiendo el siguiente enlace enlace a la documentación de sleuth, donde se profundiza mucho más en la terminología y en cómo trabaja con zipkin.

Entendiendo el proyecto de pruebas

 

microservice

El proyecto con el que vamos a trabajar consiste en tres servicios: Purchase, Inventory y Account.

Cuando llamemos a la API de purchase-service para comprar un item esta llamará a inventory-service y account-service para obtener los datos necesarios para la compra.

Vamos a instrumentar las APIs de los tres servicios y atacaremos a purchase-service, dado que internamente llamará a los otros servicios.

Si queréis investigar sobre el proyecto de pruebas, veréis que Inventory-service también expone una API que llama internamente a account-service para obtener información.

Preparando el proyecto

Lo primero que vamos a necesitar es instalar zipkin en nuestro equipo. Yo recomiendo hacer uso de docker e instalar la imagen de zipkin. Os dejo un enlace para hacerlo.

Ahora tenemos que configurar nuestro proyecto e instrumentar las APIs para rastrear las llamadas. Lo primero es añadir las dependencias necesarias en nuestro build.gradle (en nuestro caso haremos uso de gradle).

Vamos a añadir la configuración para conectar a nuestro zipkin local. Para ello en el fichero application.properties de src/main/resources añadimos las siguientes propiedades:

spring.zipkin.baseUrl: Url de nuestro zipkin.

spring.sleuth.sampler.percentage: La cantidad de peticiones que se rastrean. Por defecto es el 10% (0.1), como en nuestro caso no vamos a saturar el sistema ya que es un proyecto de pruebas vamos a rastrear el 100% de las peticiones.

sample.zipkin.enabled: Cuando lo tenemos a true, las trazas se envían a zipkin, en nuestro caso es lo que queremos. En caso contrario se enviarán a la consola de logs.

Vamos a ver un ejemplo básico de como instrumentamos la API de purchase. Vamos al controlador y al método purchase que será donde accedamos cuando llamemos a /purchase. Creamos un nuevo tag, añadiendo información que nos resulte interesante de rastrear.

Haremos lo mismo para inventory-service y para account-service:
https://github.com/aaguila/spring-cloud-sleuth-zipkin-example/blob/master/inventory-service/src/main/java/com/qajungle/controllers/InventoryController.java

https://github.com/aaguila/spring-cloud-sleuth-zipkin-example/blob/master/account-service/src/main/java/com/qajungle/controllers/AccountController.java

En este caso la información que metemos en el tag tiene menos sentido debido a que en la petición ya tenemos el id. Para la versión 2.0  la forma de gestionar los spans cambia, ya que ahora en vez de usar Sleuth hace uso de Brave. Podéis verlo en la guía de migración.

Con la configuración básica que hemos añadido al proyecto ya podríamos rastrear las peticiones. Vamos a ver qué resultado obtenemos.

Rastreando las pruebas de rendimiento

Lo primero que vamos a hacer es arrancar zipkin para que comience a rastrear las peticiones de nuestros servicios. Para ello basta con arrancar el docker y ejecutar el comando:


docker run -d -p 9411:9411 openzipkin/zipkin

Para nuestro ejemplo vamos a configurar nuestras pruebas en taurus ya que creo que es una herramienta que hemos usado con anterioridad en el blog. Vamos a crear un script sencillo para generar una carga durante cinco minutos de diez peticiones por segundo con cinco usuarios concurrentes que aumentaran cada minuto. Podéis verlo en el directorio de purchase-service src/test/resources/taurus-tests

Tras lanzar las pruebas vemos que las peticiones han ido correctamente, sin ningún error y con un tiempo de respuesta estable, tiene sentido debido a que no hemos generado mucha carga.

Purchase load test

Claro… esto como hemos comentado en la introducción, nos da el reporte de las llamadas a purchase-service, pero no podemos diferenciar que parte del tiempo de respuesta corresponde a que las llamadas a las APIs internas, o en caso de que hubiera un error en una API interna cual sería etc. Perdemos cierta visibilidad, y aquí es donde entra el rastreo de las peticiones, que nos dará mucha más información.

En las siguientes imágenes vemos dos peticiones a la API de purchase:

  • La primera se ha realizado al comienzo de la carga, cuando la carga era un usuario y una petición por segundo.
  • La segunda se ha realizado al final de la carga, cuando la carga eran cinco usuarios concurrentes y diez peticiones por segundo.

1s trace

9s traceMediante zipkin vemos de forma desglosada cuanto tiempo se lleva cada API, por lo que en caso de que el tiempo de respuesta sería muy alto o estaría fuera de los límites que hayamos previsto, podríamos ver si el problema está en alguna API interna. O en el caso de que haya algún error en la llamada podríamos tener más información.

Conclusiones

Instrumentar nuestros servicios webs y así poder rastrear las peticiones nos aporta información de gran interés. Ya no solo para controlar el estado de cada petición y respuesta, si no, que puede ser una gran herramienta para realizar pruebas de rendimiento si nuestro sistema se compone de servicios externos e internos.

En el caso de nuestro proyecto nos hemos ahorrado el realizar pruebas de rendimiento para dos servicios internos de forma independiente, pero en el caso de un sistema complejo basado en microservicios, el valor puede ser mucho mayor, ya que las pruebas son más realistas, nos ahorramos pruebas, tiempo etc.

Consumer-driven contract testing con Spring Cloud Contract

Introducción

Como hemos comentado en entradas anteriores, la arquitectura en microservicos cada vez está ganando más fuerza, y esto supone un reto tanto para el desarrollo como para el testing. En el post vamos a hablar sobre Consumer-driven contract (CDC) testing y cómo podemos verificar que los microservicios trabajan correctamente y según lo esperado entre ellos.

Para ello, como siempre vamos a trabajar con un proyecto de ejemplo usando Spring Cloud Contract, que podréis descargar del repositorio de GitHub.

Consumer-driven contract (CDC) testing

Conociendo la arquitectura en microservicios sabemos que el mayor reto consiste en comprobar y validar la comunicación entre los distintos servicios y para ello las estrategias más comunes son ejecutar pruebas end-to-end o crear mocks de los servicios y lanzar pruebas de integración sobre ellos.

MicroservicesCon las pruebas end-to-end vamos a comprobar la comunicación entre los servicios simulando una experiencia más cercana a el entorno de producción. Primero debemos tener levantado todo el entorno (servicios, BBDD) lo que es costoso y dificulta la trazabilidad. Por otro lado, es fácil que los problemas de comunicación hagan que nuestras pruebas se ralenticen más de lo normal o que fallen. Las dependencia de tantos componentes debilitan nuestras pruebas.

Como hemos visto en un post anterior, mockear un servicio nos elimina los problemas anteriores. Por el contrario, puede que las pruebas contra el mock sean satisfactorias, pero en el entorno de producción la aplicación falle debido a un mal diseño de los mocks.

 

 

cdc testingCon Spring Cloud Contract diseñaremos los contratos de los servicios. El contracto sencillamente es la definición del comportamiento de nuestro servicio en base a una request. Con esa definición se van a generar teses a nivel de proyecto para validar los contratos, así como, se publicará el Stub en nuestro repositorio maven contra el que lanzaremos las pruebas de los clientes del servicio (como vemos en la imagen). No obstante, lo vamos a ver ahora y quedará mucho más claro.

Entre los beneficios que hemos visto, utilizar CDC testing con Spring Cloud Contract nos aporta:

  • Poder desarrollar nuestro servicio mediante metodología ATDD.
  • Una documentación actualizada y “honesta” de las APIs que conforman nuestro sistema.
  • Disponer de un mayor control de los clientes que consumen nuestros servicios. Debido a que sabremos quien realiza las pruebas contra los Stubs publicados.
  • Los contratos publicados siempre actualizados, debido que en el proceso de publicación se encuentra integrado en el proceso de “build”.

Entendiendo el proyecto de pruebas

 

project_services

El proyecto con el que vamos a trabajar consiste en un sistema de dos servicios. Inventory-service y Account-service.

Account-service expone una API que devuelve los datos de los usuarios e inventory-service expone una API que devuelve el inventario de un usuario vendedor.

Inventory-service consume account-service para obtener los datos del usuario de los items que va a devolver cuando le llamen, por lo que vamos a lanzar las pruebas sobre el Stub publicado de account-service.

Inventory-service publica su Stub para que cualquier cliente futuro pueda ejecutar las pruebas contra él. Así que vuestra imaginación puede volar y ayudaros a construir un nuevo servicio siguiendo los pasos del CDC 😛

 

Preparando el proyecto

Antes de ponernos a definir los primeros contratos vamos a ver que dependencias necesitamos en nuestro proyecto. Los ejemplos los vamos a ver del proyecto de account-service.

Dentro del build.gradle vamos a definir también en que directorio vamos a alojar los contratos, así como donde se guardarán las clases base de tests que utilizará para generar los teses de verificación de los contratos.

Definiendo los contratos

Siguiendo con account-service vamos a ver el contracto que hemos definido para devolver los datos de un usuario en base a su id, en /src/test/resources/contracts/ShouldReturnAccountById.groovy

Los contratos tienen una semantica muy clara y diferenciada. Vemos como definimos tanto la request que esperamos como la response que devolvemos para esa request.

Con este contracto estamos definiendo que para cuando llamen al endpoint: /account/[0-9]+ vamos a responder con un estado 200 y con un json que contien esos campos y datos.

Vemos que la url tiene un regex, por lo que un ejemplo de una llamada que la cumpla sería: /account/12345.

Es un contrato sencillo, y con esto tanto desarrollador (para comenzar el desarrollo) como el cliente (para consumirlo) ya sabrían cual sería el endpoint, así como los datos que tienen que mandar y los que van a recibir.

Podéis ver toda la información del DSL de los contratos en el siguiente enlace.

Si seguimos la metodología ATDD, sabemos que deberíamos hacer un desarrollo mínimo para poder lanzar los teses y que fallen e ir refactorizando hasta que los teses no fallen.

Veamos el paso de generar los contratos y publicar el Stub. Lanzando el comando generateContractTests va a autogenerar los teses de validación del contracto que hemos hablado anteriormente.


➜ account-service git:(master) ./gradlew generateContractTests
Starting a Gradle Daemon (subsequent builds will be faster)
:copyContracts
:generateContractTests

Y cuando lanzamos el comando install publicará en nuestro maven local (.m2) el Stub. Veámoslo:


➜ account-service git:(master) ./gradlew clean install
:clean
:compileJava
:compileGroovy NO-SOURCE
:processResources NO-SOURCE
:classes
:jar
:copyContracts
:generateClientStubs
:verifierStubsJar
:install

Si accedéis a .m2/repository/com/qajungle/account-service/0.0.1-SNAPSHOT podréis ver el Stub:

account-service-0.0.1-SNAPSHOT-stubs.jar

Desarrollando y ejecutando los teses

El cliente (inventory-service) consume account-service para obtener los datos del usuario del inventario. Veamos la clase de servicio y el gateway sobre el que tendremos que desarrollar el test:

/src/main/java/com/qajungle/services/InventoryService.java

/src/main/java/com/qajungle/gateway/AccountGateway.java

Como vemos AccountGateway.java es el encargado de realizar la llamada al API de account-service. Por lo que en este punto es donde tendremos que atacar contra el Stub publicado por el servicio. Veamos el test:

/src/test/java/com/qajungle/services/InventoryServiceTest.java

Lo más importante en este punto es ver que estamos atacando al Stub del servicio mediante el tag:


@AutoConfigureStubRunner(ids = "com.qajungle:account-service:+:stubs:8082", workOffline = true)

Si lanzamos el test del proyecto veremos que todo ha ido correctamente:


➜ inventory-service git:(master) ./gradlew -Dtest.single=InventoryService test
:compileJava
:compileGroovy NO-SOURCE
:processResources
:classes
:copyContracts
:generateContractTests
:compileTestJava
:compileTestGroovy
:processTestResources
:testClasses
:test

BUILD SUCCESSFUL

Cambiemos el contracto en account-service para comprobar que lanza contra el Stub. Por ejemplo cambiendo el valor de un campo.


➜ inventory-service git:(master) ✗ ./gradlew -Dtest.single=InventoryService test
:compileJava UP-TO-DATE
:compileGroovy NO-SOURCE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:copyContracts
:generateContractTests
:compileTestJava UP-TO-DATE
:compileTestGroovy
:processTestResources UP-TO-DATE
:testClasses
:test


com.qajungle.services.InventoryServiceTest > should_return_seller_information FAILED
org.junit.ComparisonFailure at InventoryServiceTest.java:28

org.junit.ComparisonFailure: expected:<“[aritz]”> but was:<“[fail]”>

El resultado es que el test fallará con un ComparisonFailure en este caso.

Conclusiones

El hacer uso de contract testing nos proporciona unos beneficios que no podemos encontrar en otras estrategias para el testing de servicios. Es muy importante sacar los valores que nos aporta ya no solo en no depender del entorno, de los teses E2E, si no de poder desarrollar mediante ATDD, disponer de una documentación fidedigna y llevar un mayor control de quien consume nuestros servicios.

A día de hoy solo he probado Spring Cloud Contract, pero me consta que existen otras herramientas como Pact, que espero probar en un futuro cercano. No obstante como habéis podido comprobar Spring Cloud Contract no es complejo de utilizar.

Rest API performance testing con Taurus

Introducción

En el post vamos a tratar paso a paso como comenzar a lanzar pruebas de rendimiento sobre Rest API con Taurus, ejecutar una prueba y analizar los resultados. Por si queréis realizar las pruebas vosotros también os proporciono el código que podréis descargar de GitHub.

Durante los años que he trabajado con pruebas de rendimiento he podido probar varias herramientas de pago y libres. Desde hace unos pocos meses suelo hablar con Federico Toledo, sobre el mundo del testing en general, pero sobre todo le suelo hacer consultas sobre pruebas de rendimiento.

Entre varias recomendaciones, salió Taurus. Taurus es una herramienta open source que nos permite automatizar de forma sencilla nuestras pruebas. Lo interesante de Taurus es que las pruebas se definen mediante YAML, lo que hace que los teses sean más legibles y fáciles de escribir.

Otra característica interesante de Taurus es que nos permite ejecutar pruebas de otras herramientas como JMeter, Gatling, Selenium, JUnit etc. Podéis encontrar la lista en el siguiente enlace.

Entorno de pruebas

Vamos a levantar un servidor Rest API sobre el que vamos a lanzar las pruebas de rendimiento, Json-server.

Lo ideal sería tener una máquina con el servidor y otra con la ejecución de la prueba, si esto no os es posible, o si quereís hacer todo más rápido con fines de aprender, está bien instalar todo junto. No perdamos de vista que al momento de ejecutar pruebas de verdad esto lo debeis separar, para que el sistema y la herramienta de generación de carga no compitan por recursos.

Por lo que si queréis experimentar conmigo, podéis ver como instalarlo en el enlace anterior. 🙂

Objetivo de las pruebas

  1. Aplicación
    Vamos a imaginarnos que tenemos una aplicación en la que almacenamos datos de los posts publicados. Disponemos de una API Rest para que los consumidores puedan listar los posts o buscar un posts concreto. Por otro lado internamente usamos la API para publicar nueva información.
  2. Carga estimada
    Tras un análisis inicial para saber el número de peticiones por minuto (througput) que esperamos en nuestra API se estima que en el peor de los casos puede ser de:
    – 1200 rpm (peticiones por minuto) y una concurrencia de 20 usuarios en el caso del listado de posts (GET: /posts)
    – 1 rpm y una concurrencia de 1 usuario en el caso de la publicación de posts (POST: /posts)
    Lo ideal sería que el tiempo de respuesta fuera inferior a 40 ms.
  3. Que probar
    Tras el análisis inicial debemos saber cual es nuestro objetivo real de las pruebas. Podemos realizar pruebas de rendimiento para ver el porqué la aplicación no responde en un tiempo aceptable, realizar pruebas periódicas para ver que el tiempo de respuesta no se degrada, hasta pruebas para ver el número de máquinas que hacen falta para soportar una carga esperada.
    En este caso vamos a comprobar que para la carga esperada nuestra aplicación responde en un tiempo de respuesta aceptable y sin errores, es decir load testing o pruebas de carga.

Definiendo los casos de prueba

Vamos a configurar la prueba con dos escenarios diferentes:

1.- Escenario de peticiones al listado de posts. 20 usuarios concurrentes con un throughput de 20 rps (en Taurus el throughput se define por segundos), de tal forma que 20 * 60 = 1200 rpm.

2.- Escenario de peticiones al listado de posts. 1 usuario concurrente con un throughput de 1 rps.

En ambos casos para poder ver si el tiempo de respuesta se degrada con el tiempo vamos a ejecutar la prueba durante 1 hora, con una rampa de usuarios de 5 minutos, es decir, cada minuto se añadirán 4 usuarios, de tal forma que cuando pasen los 5 minutos tendremos la concurrencia de 20 usuarios.

La configuración de la prueba es bastante legible, no obstante os dejo el siguiente enlace donde podéis ver la sintaxis de configuración.

Ejecutando las pruebas

En la configuración de las pruebas de carga los dos escenarios se encuentran en la misma configuración “load-api-testing.yml”. Vamos a ver como ejecutarlos.

Para ejecutar las pruebas de carga tenemos que ejecutar el siguiente comando dentro del directorio de las pruebas:

bzt load-api-testing.yml 

taurus execution

Taurus nos proporciona un visor por consola aportando la información básica del estado de la prueba en vivo.

El panel izquierdo de la imagen nos muestra las peticiones que se hacen sobre el servidor api rest que hemos levantado para realizar las pruebas.

Taurus mediante su configuración nos permite subir los resultados al visor de resultados de nuestra cuenta de Blazemeter. Os dejo el siguiente enlace para que veáis como se configura.

 

Para conseguir unos datos objetivos sería deseable ejecutar cada prueba un mínimo de tres veces, siempre y cuando dispongamos de tiempo para poder realizarlas. Esto es normal ya que en una única ejecución hemos podido encontrarnos con problemas externos que pueden desprestigiar los resultados.

Analizando los resultados

Como hemos comentado anteriormente Taurus nos permite subir los resultados al visor de nuestra cuenta de Blazemeter. Vamos a analizar los resultados de nuestra prueba centrandonos en los datos más importantes para el objetivo del post.

Reporte temporal

grafico resultados

A simple vista se ve que el tiempo de respuesta va aumentando con el tiempo, superando incluso el límite que habíamos definido como ideal, RT < 40ms. Esto puede ser debido a dos motivos:

  1. Con el tiempo el sistema se degrada debido a la concurrencia de 50 usuarios.
  2. Debido que metemos un nuevo dato cada segundo, la base de datos crece,  y por lo tanto el tiempo de respuesta aumenta.

Para descartar la primera opción se debería hacer una prueba sin añadir datos cada segundo a la base de datos, de esa forma veríamos si el tiempo de respuesta se mantiene en un rango aceptable.

No obstante si ese fuera el caso, a futuro ibamos a tener el mismo problema ya que en algún momento ibamos a tener ese número de datos en la base de datos, y llevaría a picos en el tiempo de respuesta, como podemos ver entre las 17:05 y 17:15.

Estado de las peticiones

grafico resultados

Hemos visto el gráfico de datos, esto nos ayuda a ver la estabilidad del entorno en las pruebas, ahora vamos a analizar los datos del tiempo de respuesta.

En muchas ocasiones valoramos los datos por la media (avg), en este caso la media del tiempo de respuesta (27ms). Si únicamente nos fijaramos en la media nuestra prueba sería exitosa, y es ese el caso?

En la pestaña de Request Stats nos dan los resultados de los percentiles. Los percentiles nos muestran una medida bajo la cual se encuentra un porcentaje de la muestra, es decir, en nuestro caso el p95 (95%) de la muestra se encuentra por debajo de 73 ms.

Como vemos los resultados no son lo que esperábamos, si lo que deseamos es que la mayoría de lo usuarios tengan un tiempo de respuesta menor que 40 ms. Dista bastante de la media de 27ms.

Os dejo un enlace muy interesante sobre las métricas para performance testing:

https://www.federico-toledo.com/promedio-desviacion-estandar-y-percentiles-metricas-para-testing-de-performance/

Errores

errores

Si volvemos a ver la gráfica del reporte temporal podemos ver que sobre las 16:45, cuando el entorno comienza a desestabilizarse, empezamos a tener picos de errores.

Si vamos a la pestaña de errores podemos ver el número de errores. En el caso de nuestras pruebas la mayoría de errores vienen debido a problemas de conexión o respuesta con el servidor, muestra de que no tolera la carga.

Conclusiones

A pesar de que disponemos de una gran cantidad de herramientas, Taurus nos permite definir las pruebas de forma muy sencilla y reutilizable. Nos aporta los datos necesarios para obtener unos resultados concretos.

No lo he comentado anteriormente pero Taurus se puede integrar de forma muy fácil a nuestro CI, os dejo el siguiente enlace.

Por otro lado, teniendo claro el objetivo de nuestras pruebas y con un buen análisis de los resultados podemos llegar a conclusiones acertadas.

API Mocking con Apiary y SoapUI

Introducción

En muchas ocasiones en las que desarrollamos aplicaciones es necesario consumir APIs externas a nuestra aplicación. Este hecho a veces implica que en nuestros entornos de desarrollo o de pruebas tengamos que usar un sandbox, apuntar al entorno de pruebas de la API o, en caso extremo, apuntar al entorno de producción de la API.

Pero vemos varios puntos en contra de su uso:

  • Dependencia de sus entornos. Cuando su entorno no esté disponible no podremos ejecutar nuestras pruebas ya que es probable que nos fallen.
  • Tiempo de ejecución de los teses. El que nuestros teses tengan que comunicarse con servicios externos hará que el tiempo de ejecución aumente. Podéis ver una charla interesante sobre la reducción de tiempo de teses.

Una alternativa es simular la respuesta que esperamos, es decir, mockear la API externa.

API mocking

Como hemos comentado anteriormente, cuando hablamos de mockear una API, estamos queriendo simular cada respuesta que nos devuelve en base a la petición que hagamos.

La ventaja de mockear la API es que no necesitamos depender de que el entorno esté disponible. Otra ventaja importante es que reduciremos el tiempo de las pruebas, obviamos cualquier lógica de la API, ya que devolvemos una respuesta directa a la petición. Si el servicio mock lo tenemos en local o en nuestra red interna, la comunicación será más rápida.

Como desventaja se encuentra el hecho de que no realizamos las pruebas contra un entorno “real”, por lo que ante cualquier cambio en la API, tendremos que actualizar nuestro mock siempre y cuando el cambio nos afecte.

Una de las tareas más importantes es la de definir bien los diferentes casos, es decir, los diferentes usos o peticiones que vamos a realizar a la API.

Vamos a ver dos herramientas (con sus ventajas y desventajas) con las que mockear una API.

APIARY

URL: https://apiary.io/

Apiary es una herramienta online, que mediante un editor sencillo nos permite diseñar la respuesta que queremos devolver a una petición concreta.

apiary editor

Veamos el ejemplo default de una petición GET:

Cuando hagamos una petición GET al path /questions nos devolverá una respuesta 200 con listado de preguntas en formato json.


Apiary nos proporciona un inspector de las peticiones que se realizan al mock server, de tal forma que podemos tracear tanto las peticiones correctas como las incorrectas.

apiary inspector
apiary inspector

Ventajas

Apiary nos aporta una interfaz muy sencilla para editar nuestra API mediante API Blueprint. Otro de los puntos favorables es que mediante el editor generaremos la documentación de nuestro Mock API.

El disponer de un inspector nos ayudará a trazear las diferentes peticiones que se realizan a nuestro mock. En mi caso esto ha sido muy interesante, ya que en un proyecto pude ver peticiones que no tenía contempladas para mockear y testear.

Es un servicio SaaS, por lo que no necesitamos preocuparnos de alojarlo y gestionarlo en nuestros entornos.

Desventajas

La mayor desventaja de Apiary es que es muy simple, es decir, no nos permite meter lógica más allá de lo que es devolver siempre la misma información.

Por ejemplo: Si realizo la petición GET /questions, siempre vamos a recibir el mismo body.

Y si por ejemplo nos interesa disponer de cierta variación de datos como un body aleatorio o generado de datos de la base de datos, no vamos a tener la robustez que buscamos. No obstante, es una muy buena herramienta si no queremos mockear a ese nivel.

SOAPUI

URL: https://www.soapui.org/

Curiosamente yo había usado SoapUI para testear servicios web SOAP, pero no sabía que SoapUI permitiera mockear una API y menos para API REST.  Lo permite y encima en la versión OpenSource.

Para hacer la prueba vamos a mockear el API Mock que hemos publicado en Apiary, así nos ahorraremos el crear nosotros un API. Obtenemos las preguntas mediante una llamada GET al path /questions

soap ui request

Cuando generamos una nueva petición, nos permite crear el REST Mock Service. Lo que me gustaría detallar es que en el caso de SoapUI sí se nos permite generar respuestas dinámicas.

Veamos un ejemplo, donde la primera key de votes tiene el valor ${votes1}

Las respuestas dinámicas las podemos crear mediante el desarrollo de scripts a nivel de petición o de respuesta, ya que SoapUI nos permite definir más de una respuesta por petición.

Vamos a hacer un ejemplo de un script que va a obtener un dato de la base de datos para setearlo en la variable ${votes1} de la respuesta definida.

soapui_script

Si hacemos la petición al servidor, vamos a ver como nos va a setear el resultado de la base de datos.

El lenguaje de scripting es “Groovy script” por lo que si has desarrollado en Groovy o en Java no te va a ser difícil.

Ventajas

SoapUI nos va a permitir desarrollar lógica en nuestros mocks por lo que si necesitamos mayor dinamismo en las respuestas nos va a ser de gran ayuda.

Desventajas

Como desventaja frente a Apiary, es necesario instalar SoapUI en el servidor para poder ejecutar el servidor mock (se puede hacer desde línea de comandos). Y no disponemos de un “inspector”, lo que si es posible es guardar la información del log de SoapUI.

Conclusiones

Si lo que necesitamos es mockear una API sin necesidad de disponer diferentes respuestas, Apiary nos puede ser de gran ayuda. Sin embargo, en el caso de querer “ir más allá” necesitaremos añadir cierta lógica: SoapUI es una opción ideal.

No obstante, recordemos que nuestra intención es mockear, no crear otro servicio web.

 

Testing de microservicios – Estrategias

Introducción

Cada vez va cogiendo más fuerza el desarrollo usando arquitectura de microservicios por lo que el mundo de la calidad de software ha necesitado de nuevas estrategias. En este post hablaremos diferentes estrategias que se utilizan para garantizar la calidad de los microservicios.

Breve introducción a los microservicios

La arquitectura en microservicios busca desarrollar una aplicación dividiéndola en pequeños servicios (cada uno con su lógica de negocio) que corren de forma autónoma y que se comunican entre sí. Pero, ¿Que beneficios e inconvenientes nos aporta frente a la arquitectura monolítica?

microservicios

Como se puede apreciar en la imagen, una arquitectura monolítica es divida en varios servicios creando así una arquitectura de microservicios, donde cada servicio se comunica entre sí aportando la funcionalidad que ofrecía en su arquitectura monolítica.

 

BENEFICIOS

  • Cada microservicio es independiente por lo que su despliegue y desarrollo también, un cambio en uno no afecta en los demás.
  • La comunicación se  realiza mediante su interfaz pública (API) por lo que el lenguaje de desarrollo de los microservicios es independiente.
  • Mayor gestión de la escalabilidad, podemos escalar solo los microservicios que reciban más carga.

INCONVENIENTES

No todo son beneficios y es que la arquitectura en microservicios también nos aporta ciertos inconvenientes y retos, muchos de ellos debido a la introducción del concepto de sistemas distribuidos.

  • En el caso que una aplicación se componga de muchos microservicios es necesaria una mayor coordinación en los despliegues.
  • Será necesario una buena monitorización debido a que nos basamos en una arquitectura con un flujo muy dependiente de la comunicación. Un error en un punto del flujo tiene que ser rápidamente reconocible.
  • Garantizar la consistencia de los datos es un punto a tener en cuenta.
  • La saturación de la red puede afectar gravemente en el rendimiento de nuestra aplicación.
  • Las estrategias de calidad son mucho más complejas que en sistemas monolíticos debido a que no solo tendremos que testear los microservicios de forma independiente si no la funcionalidad en el flujo de los datos.

Estrategias de testing 

Como hemos dicho en la introducción, el mundo de la calidad del software ha tenido que aplicar nuevas estrategias de calidad para el reto que supone garantizar la calidad de las aplicaciones con arquitectura de microservicios.

En mi opinión el reto no se encuentra en el testing individual de cada microservicio, si no, en la integración y en la garantía de la consistencia de los datos. En comparación con los sistemas monolíticos, en una arquitectura por microservicios o distribuida el QA/Tester va a necesitar de un mayor conocimiento de la arquitectura y del flujo existente, debido a que es necesario controlar y verificar la información en todos los puntos de comunicación así como la funcionalidad de todos los microservicios.

La estrategia principal o las pruebas que se realizan son las siguientes (teniendo en cuenta que la estrategia es muy dependiente del proyecto que nos encontremos):

  • Tests unitarios: Como en todo proyecto ya sea monolítico o no, son necesarias pruebas unitarias. Mediante las pruebas unitarias comprobaremos la funcionalidad de los métodos o módulos de código que creamos necesarios.
  • Tests de integración: Los teses unitarios prueban los componentes de forma aislada por lo que también necesitamos probar el comportamiento entre los métodos. Debemos tener en cuenta que solo probaremos el comportamiento de los métodos de cada aplicación de forma aislada (de cada microservicio), por lo que las llamadas entre microservicios las mockearemos.
  • Tests de contratos o de API: La arquitectura de los microservicios depende de la comunicación entre los diferentes servicios. Cada microservicio presenta una API que consumen los demás microservicios, cuando la diseñamos estamos definiendo un “contrato”. Debemos realizar los diferentes tests a las APIs de los microservicios para garantizar que se mantiene el “contrato”.
  • Tests E2E: E2E (end-to-end) o teses funcionales tratan de garantizar la calidad de nuestra aplicación sin la necesidad de mockear  ningún método o llamada. Un test recorrerá la funcionalidad entre todos los microservicios que requiera. Debemos tener en cuenta que son susceptibles a fallar por motivos agenos a la funcionalidad (problemas de red, perdida de datos etc.). En el caso de los microservicios se recomiendan seguir unas reglas que nos ayudarán a reducir el esfuerzo de nuestros teses:
    • Dado que las pruebas E2E son difíciles de mantener, debemos intentar centrarnos en probar solo las funcionalidades más importantes, probando las demás en teses unitarios o de integración.
    • El uso de WebDrivers como selenium para probar las funcionalidades de usuario tiene un coste grande en cuanto a mantenimiento por lo que la mayoría de las acciones del usuario podemos hacerlas simulando las llamadas a la API.
    • Debemos intentar mantener un entorno limpio para cada ejecución dado que las pruebas son muy dependientes de los datos y pruebas anteriores pueden desestimar el resultado de los teses.

Referencias

  1.  http://testdetective.com/microservices-testing/
  2. http://www.sczyh30.com/vertx-blueprint-microservice/index.html

Testing Restful API response

Introducción

Hace un tiempo publiqué un artículo sobre cómo “testear” funcionalmente la Restful API, los podéis leer en:

https://qajungle.com/restful-api-functional-testing/
http://blog.engineering.ticketbis.com/rest-api-functional-testing/

En el proyecto en el que he estado trabajando he necesitado tener que validar que el json de respuesta tenga una estructura en concreto y que los campos cumplan ciertas condiciones.

Al principio no necesitaba validar grandes cosas por lo que simplemente creé una clase utilidad que me ayudará a comprobar que el json de respuesta tendría los campos que esperábamos, pero esto suponía varios problemas o deficiencias:

  • Únicamente podría validar la existencia del campo, pero no ningún tipo de restricción.
  • Duplicidad de código a medida que se añaden más pruebas.
  • Un cambio en la definición del json implicaría refactorizar las pruebas.

Debido a estos motivos pensé que tenía que haber una forma más sencilla que permitiera suplir estas deficiencias. Me acordé de cómo cuando construimos un XML definimos su esquema de tal forma que validamos su estructura de una forma sencilla, por lo que me puse a investigar y encontré lo siguiente:

http://json-schema.org/

Mediante json-schema definimos la estructura que tiene que cumplir nuestro json por lo que podía ser una buena baza ya que nos permitiría:

  • Validar la estructura y ciertas restricciones.
  • Únicamente tenemos los esquemas por lo que evitamos duplicidad de código.
  • Un cambio en la definición únicamente nos obliga a cambiar el esquema y no refactorizar el código.

Solucionamos los problemas con los que nos hemos visto y encima mantenemos cierta independencia en lo que es la validación de la estructura del json y el test funcional.

Configuración del proyecto

Lo primero que tenemos que hacer implementar un validador json que nos permita comparar nuestro esquema con la respuesta obtenida, para ello he hecho uso del plugin:

https://github.com/fge/json-schema-validator
Lo cierto es que actualmente el plugin no tiene un mantenimiento pero está desarrollado para la versión actual del esquema v4.

En nuestro caso como hemos desarrollado sobre un proyecto grails lo añadiremos en el BuildConfing.groovy como dependencia:

dependencies {
...
compile "com.github.fge:json-schema-validator:2.2.6"
}

Clase utilidad

Para poder simplificar y no duplicar código creamos una clase utilidad:

Básicamente lo que hacemos es cargar el esquema json que hemos definido y validarlo frente a la respuesta obtenida de la llamada a la API.

Una de las características es que nos proporciona un reporte de los errores de validación por lo que nos dará información de los incumplimientos.

Definición del esquema

Siguiendo el ejemplo visto en el artículo https://qajungle.com/restful-api-functional-testing/ vamos a generar el esquema para la respuesta de los post.

Únicamente definimos las tres propiedades que esperamos: id, title, body e indicamos que son campos requeridos por lo que si en la respuesta no obtenemos el campo o es nulo no pasará la validación.

Más información sobre la definición de esquemas:

http://json-schema.org/examples.html

Validando la respuesta

Para validar la respuesta únicamente llamaríamos al método de la clase utilidad pasándole la ruta del esquema correspondiente:

JsonValidationUtils.validateJson(response.text, "src/java/schemes/post.json")

Conclusiones

Si nos encontramos con el caso que tener que validar la estructura de la respuesta obtenida de Restful API puede ser interesante utilizar algún tipo de validador ya que nos aporta mayor potencia para el testeo de la respuesta así como evitar duplicidad de código y evitarnos tener que hacer gran refactorización de nuestro código.

Bibliografía

  1. http://json-schema.org/
  2. https://github.com/fge/json-schema-validator

Restful API Functional testing

Introducción

Llevo unos días trabajando en un proyecto interno para realizar pruebas funcionales de la API que proporcionamos en Ticketbis (http://api.ticketbis.com/).

Buscando simplicidad y robustez decidimos hacer uso de Restfulapi, el plugin de grails (https://github.com/restfulapi/restful-api) que nos proporciona las clases necesarias para realizar “testing” funcional sobre cualquier API Restful. El proyecto está siendo tan interesante que me he propuesto hacer el siguiente post.

Antes de ponernos manos a la obra comentar un par de detalles:

Configuración

El plugin restfulapi nos permite hacer uso de Spock poder basar nuestras pruebas en la metodología TDD. Para ello vamos a necesitar importar el plugin net.hedtech.restfulapi.spock.

Nuestra clase deberá extender de RestSpecification.

Test petición GET

Para “testear” la llamada necesitaremos construir la petición get con las cabeceras.

get("[URL_PETICIÓN]") {
      headers[[CABECERA]] = [VALOR_CABECERA]
}

Y finalmente comprobar que la respuesta que obtenemos de la petición get es la esperada:

  • Obtenemos las cabeceras de la petición mediante:
    responseHeader([HEADER])
  • Para obtener el estado de la petición usaremos:
    response.status

Si queremos validar el JSON de la respuesta podemos hacer uso de grails.converters.JSON.

def json = JSON.parse response.text

Veámoslo con el siguiente ejemplo donde validamos una petición get mediante la que obtenemos el post de un blog.

Test petición PUT

Por último vamos a ver como podemos "testear" una petición PUT de la API.

Construiremos la petición PUT con las cabeceras y el cuerpo.

put("[URL_PETICIÓN]") {
headers[[CABECERA]] = [VALOR_CABECERA]
body {
"""
{
"[CLAVE]": "[VALOR]",
"[CLAVE_N]": "[VALOR_N]"
}
"""
}
}

Vemos como en el siguiente ejemplo validamos la respuesta de una petición put en la que modificamos un post del blog.

Conclusiones

El plugin restfulapi de grails nos permite de una forma sencilla probar funcionalmente las diferentes peticiones a la api:

  • GET
  • POST
  • PUT
  • DELETE

El hecho de que proporcione Spock framework nos aporta mayor sencillez y legibilidad a nuestros "teses".

Bibliografía:

1. https://github.com/restfulapi/restful-api
2. https://github.com/spockframework/spock-example