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!

 

 

 

Model based testing mediante Graphwalker

Introducción

Llevo un tiempo investigando nuevas formas para definir y diseñar las pruebas en nuestro equipo. La idea principal es disponer de un modelo de diseño de pruebas que sirva como punto de encuentro entre el rol de producto, desarrollador y QE.

En este post vamos a hablar sobre Model-based testing o MBT que se nos ayuda a acercarnos bastante a la idea que he comentado arriba.

Y como siempre, vamos a verlo en práctica, para ello vamos a hacer uso de Graphwalker, una herramienta de MBT.

Podéis descargar el proyecto desde mi Github. Esta vez, veréis que el proyecto únicamente tiene logs para ver el flujo de secuencia del test. Usarlo como base y experimentad!

Model based testing (MBT)

En Model-based testing (MBT) se define nuestro SUT en base a vértices o vertex y la transición o edge entre estos.

model based testing example

El modelo, se compone de tres vértices o vertex:

  • v_App_closed, como vértice inicial y final.
  • v_App_running.
  • v_Display_preference

De tal forma que en el caso de este modelo (que es sencillo) si queremos probar el 100% de los vértices y las transiciones, el test path más lógico sería:

Que para llegar de uno a otro requiere de ciertas transiciones o edge:

  • e_Start_app, transición inicial.
  • e_Close_app.
  • e_Open_preferences.
  • e_Close_preferences.

De tal forma que en el caso de este modelo (que es sencillo) si queremos probar el 100% de los vértices y las transiciones, el test path más lógico sería:

mtb example flow

Como hemos dicho, nuestro caso es muy sencillo y es fácil identificarlo, pero lo normal es tener modelos mucho más complejos.
Para estos casos es cuando las herramientas de MBT como Graphwalker, nos ayudan a generar los test path.

Normalmente las herramientas MBT nos proporcionan dos formas de generar los test path.

OFFLINE

En la generación offline, los test path se generan previamente, de tal forma que puedan ser ejecutados con posterioridad por la herramienta.

ONLINE

En la generación online, los test path se generan automáticamente en tiempo de ejecución. Para este caso, la herramienta o el framework de MBT estará acoplado al código del test, pero esto nos aporta ciertas ventajas.

Vamos a verlo en la práctica!

Añadir Graphwalker a nuestro proyecto

Para añadir Graphwalker a nuestro proyecto, únicamente necesitamos añadir las dependencias y el plugin:

Lo veremos más adelante, pero mediante maven generamos las interfaces que tendrán que implementar nuestras clases.

Diseñando el modelo

Para diseñar el modelo vamos a usar yEd, que nos permitirá diseñar el modelo gráficamente, siendo totalmente compatible con Graphwalker.

Imaginemos que tenemos una aplicación, de venta de entradas. Formamos parte del equipo de pagos y tenemos una arquitectura basada en microservicios.

La idea es probar la feature de la compra de una entrada a través de las APIs.

mbt purchase

Nuestro modelo se va a componer de cinco vertex:

Crear usuario -> Añadirle el medio de pago -> Crear el carrito de compra del usuario -> Añadir la entrada al carrito de compra -> Comprar el ticket

Si nos fijamos en los edges entre los vértices, podemos ver pueden ir acompañados de “cierta lógica”.

Es muy importante nombrar los edged, con una “e” por delante y los vertex con una “v”, de esta forma Graphwalker podrá identificarlos.

mbt exa 1

Por ejemplo, en el caso del edge e_AddPaymentMethod, vemos el código:

/paymentMethod=’paypal’; validPaymentMethod=true;

Esto quiere decir que Graphwalker, cuando vea el símbolo /, todo lo que venga posteriormente lo va a interpretar como código javascript, es decir, en esta transición vamos a setear valor para dos variables.

mbt ex 2

Si nos fijamos en el siguiente edge, vamos a ver un código un poco diferente:

[validPaymentMethod]

Graphwalker cuando vea los símbolos […], lo va a interpretar como un condicional, es decir, va a comprobar que el valor de validPaymentMethod sea true.

Al ser código javascript, tanto el código como las condiciones pueden ser mucho más complejas, depende de las necesidades de nuestras pruebas.

Os dejo un enlace de la documentación de Graphwalker donde podreis verlo con mucho más detalle.

GENERANDO LA INTERFAZ DEL TEST

Para generar la interfaz del modelo para nuestras pruebas, vamos a guardar el fichero graphml de yEd en el directorio src/test/resources/ del proyecto.

Ejecutamos el comando de maven:

mvn graphwalker:generate-test-source

Esto nos generará en el directorio target la interfaz que tendremos que implementar en nuestras pruebas para lanzarlo con Graphwalker:

interface

Desarrollando y ejecutando las pruebas

En el test definiremos el algoritmo con el que queremos que se genere la secuencia de nuestro test.  La interfaz nos indicará los métodos de los que se compone nuestro test.

En el test definiremos el algoritmo con el que queremos que se genere la secuencia de nuestro test. En nuestro caso:

@GraphWalker(value = "quick_random(edge_coverage(100))", start = "e_CreateUser")

  • quick_random: Ejecutará el camino más corto, lo va decidir mediante el algoritmo de Dijkstra.
  • edge_coverage: Que ejecute el 100% de los edges que hemos definido en el modelo.

Es decir, en nuestro caso va a ejecutar todos los edges, y por cada vuelta va calcular cual es el camino más rápido.

Esta parte es la más interesante e importante, debido a que depende el algoritmo de ejecución que definamos probaremos más o menos, es decir, decide nuestra estrategia de testing.

Os dejo un enlace donde podéis ver las diferentes opciones de ejecución.

Para ejecutar las pruebas:

mvn graphwalker:test

test results

Como resultado vemos que se han ejecutado el 100 de los vertex y los edges, por lo que hemos lanzado pruebas con una cobertura del 100%.

Conclusiones

Definir las pruebas con MDT puede ayudarnos a alinearnos mejor con producto y con desarrollo, dado que los modelos clarifican bastante el flujo de testing de nuestro SUT.

El poder integrarlo con herramientas como Graphwalker en donde podemos definir el algoritmo de ejecución de las pruebas, nos aporta una gran flexibilidad a nuestra estrategia de testing.

Imaginemos que queremos hacer smoke testing, bastaría con definir la ejecución como:

e_start(reach_vertex())

Y si por ejemplo queremos hacer pruebas de estabilidad podemos añadirle tiempo de duración a nuestras pruebas:

random(time_duration())

Otro de los puntos fuertes, es que no está casado con ningún tipo de testing, es decir, al definir únicamente el flujo o los pasos del test, podemos realizar desde test de UI mediante selenium, a APIs, performance etc.

Aislando nuestras pruebas con Testcontainer

Introducción

Hace unos días escribí sobre las pruebas aisladas e integradas. Uno de los mayores retos en nuestra estrategia de testing es el hecho de intentar aislar nuestras pruebas.

Hemos visto cómo aislarlas mediante contract testing, pero; ¿Cómo aislamos nuestras pruebas de integración o las funcionales?

En este post vamos a hablar sobre Testcontainer, una librería de java que nos provee instancias ligeras de cualquier contenedor de Docker (bases de datos, navegadores, servicios etc.).

Como siempre vamos a ver los ejemplos con un proyecto que podréis descargar desde GitHub.

Vamos a ello!

Aislando las pruebas del proyecto

App architecture
El proyecto de ejemplo es sencillamente una aplicación conectada a una base de datos MySQL y que consume a encoder-service,  un servicio propio, que es usado por todas nuestras aplicaciones.

Vamos a desarrollar pruebas de integración. Comprobaremos que nuestra aplicación comunica correctamente tanto con la base de datos, como con el servicio.

Mediante Testcontainer cuando ejecutemos las pruebas, se levantará un contenedor Docker de MySQL y otro de encoder-service.

Una de las preguntas que os podéis hacer, es el porqué levantar un contenedor del servicio, si lo podemos mockear. Hay ocasiones en las que cuando realizamos pruebas, necesitamos recibir un comportamiento real, y no uno esperado. Este ejemplo pretende acercarse a esa realidad.

Test architecture

Añadiendo Testcontainer al proyecto

Vamos a añadir dos dependencias a nuestro proyecto. Las dependencias generales de Testcontainer, y la dependendencia de testcontainer de MySQL.

Además de añadir las dependencias de Testcontainer, deberemos tener las dependencias del conector de MySQL y Spring data.

Pruebas de integración sobre MySQL Testcontainer

Lo bueno de utilizar Spring Data JPA,  es que nos facilita la implementación y por tanto el uso de la capa de datos. Testcontainer se adapta muy bien a Spring Data JPA debido a que nos provee un driver jdbc que gestiona el contenedor de base datos.

Lo que nos interesa es ejecutar el contenedor de MySQL en tiempo de test, por lo que la configuración la vamos a añadir en el directorio test.

En la configuración de JPA, es importante destacar la propiedad de ddl-auto. El valor que vamos a indicarle es create-drop, de esta forma cuando se instancie el contenedor, se creará la base de datos con nuestra entidades.

Para las pruebas vamos a añadir unos datos iniciales a la base de datos, por lo que cuando se creen las tablas, se ejecutará el fichero data.sql. Para ello es importante que la propiedad initialization-mode tenga el valor always, en la configuración de datasource.

Lo más importante es driverClassName, donde indicaremos el driver de Testcontainer. En la propiedad de la url de conexión, el hostname, el puerto y el nombre de la base de datos van a ser ignorados.

El test que es muy sencillo, únicamente comprueba que se almacena y se leen los datos de la base de datos. Lo importante es sacar la conclusión de que el uso de testcontainer es totalmente transparente para el test, es decir, el test no sabe que está ejecutando las pruebas contra un contenedor de MySQL. Por lo que sí tenemos la necesidad de cambiar de base de datos o de versión de la base de datos, nuestras pruebas no se van a ver afectadas.

testcontainer results

 

Pruebas de integración sobre enconder-service Testcontainer

Primero, hemos tenido que “dockerizar” el encoder-service y añadirlo a nuestro local registry. Ahora en la configuración test de nuestra aplicación vamos a añadir el fichero docker-compose, con la configuración mínima para instanciar el contenedor de nuestro servicio.

En este caso, nuestro test si va a estar acoplado a las dependencias de Testcontainer, debido a que tenemos que hacer uso de docker compose.

Como hemos comentado, dado que queremos instanciar el contenedor de nuestro servicio desde el fichero de docker-compose.yml, vamos hacer uso de DockerComposeContainer, indicando el nombre del servicio y el puerto. En el test, obtenemos la url y el puerto del contenedor mediante getServiceHost y getServicePort. Realmente es muy sencillo.

testcontainer result encoder service

Conclusiones

Testcontainer nos permite aislar nuestras pruebas de forma muy sencilla. Mediante Spring data JPA y Testcontainer nuestras pruebas de integración de base de datos estarán totalmente desacopladas.

La mayor pega puede ser el hecho de que si queremos hacer uso de Docker compose, nuestras pruebas no van a disfrutar de la virtud de estar desacopladas de Testcontainer.

Al aislar las pruebas reducimos el tiempo de prueba, debido a que no necesitamos hacer conexiones reales y reducimos el número de falsos positivos por fallos en el otro extremo.

El sistema de CI/CD se puede ver muy beneficiado, dado que no vamos a necesitar desplegar todos estos servicios, BBDD etc. en nuestro entorno, por lo menos en entornos previos al stage o preproducción, y los desarrolladores podrán probar con mayor fiabilidad su código.

Además, Testcontainer nos proporciona librerías para hacer pruebas de UI, por ejemplo con Selenium. Y bueno… como habéis visto en el caso de encoder-service, cualquier aplicación dockerizada se podrá instanciar con testcontainer.

Mutation testing – PIT nuestro gran amigo

Introducción

No siempre las pruebas unitarias que realizamos dan la cobertura suficiente a nuestro código, no nos engañemos. Allá por los años 70-80 se creo un nuevo concepto de testing denominado mutation testing. Consistía en modificar ciertas lineas de nuestro código, para posteriormente probar si en realidad fallaba o no.

futurama_mutantesEstos cambios en el código se denominan mutantesy como si de un juego se tratará, debemos matarlos, es decir, si los teses fallan, se dice que han matado a los mutantes. Cuantos más mutantes matemos, mejor.

En este post vamos a ver como se realiza mutation testing mediante PIT, una herramienta que se va a encargar de realizar las mutaciones y ejecutar nuestros teses unitarios para ver si logra matar todos los mutantes.

Para ello, he dejado en mi github el proyecto con el que he realizado las pruebas. Podéis descargarlo y jugar a matar mutantes 😉

El código es “refactorizable”, lo sé, pero lo que he intentado es disponer de código sencillo que nos permita jugar con las pruebas unitarias y las mutaciones, lo vamos a ver.

Mutación del código

Podéis ver todos los tipos de mutaciones que realiza PIT en el siguiente enlace, pero vamos a ver una mutación sencilla, para ayudarnos a entender mejor lo que son las mutaciones en código.

La mutación de limites condicionales, consiste en cambiar los operadores relacionales de nuestras sentencias. Así comprobaremos que nuestras condiciones están bien construidas. Lo vemos en la siguiente tabla:

Original conditional Mutated conditional
< <=
<= <
> >=
>= >

En nuestro código tenemos el siguiente condicional:

De forma que la mutación condicional que se hará será:

Cuando ejecutemos las pruebas de mutación, PIT se encargará de realizar todas las mutaciones. Por defecto se aplican las mutaciones básicas, pero si queremos ser más especificos o llegar un mayor nivel de mutación, PIT nos ofrece una lista de mutaciones que tendremos que activar por configuración.

Lanzando las pruebas de mutación

A pesar de que las pruebas de mutación nos ayudan a detectar errores en nuestro código, también son muy útiles para comprobar si nuestras pruebas unitarias son correctas o garantizan la cobertura necesaría, por lo que son un gran apoyo.

Veamos la lógica de nuestro código y las pruebas unitarias que garantizan que el código funcione correctamente. Para el producto que queremos comprar, se comprueba que haya la cantidad disponible y en caso correcto se decrementa en 1 el valor de la cantidad y se devuelve. Sencillo.

Nuestras pruebas unitarias comprobaran que se descuentan correctamente los productos y que no podemos comprar más productos de los que dispone la máquina.

Si ejecutamos la pruebas unitarias, nos darán un resultado verde, es decir, todo funcional correctamente! (Según nuestras pruebas)

Unit testing result

Ahora es el turno de lanzar nuestras pruebas de mutación. Para ello, nos situamos en la raiz del proyecto y ejecutamos:
➜ mutation-testing git:(master) ./gradlew pitest


pit coverage

Las pruebas generan un reporte que se almacena en {project}/build/reports/pitest/. Vamos a analizarlos.

En nuestro proyecto de pruebas tenemos dos clases en src/java. En este caso la clase ProductCode es un enum, por lo que vamos a necesitar cobertura de test.

La lógica se encuentra en la clase VendingMachine.java, que es la encargada de gestionar la tienda.

 

En nuestras pruebas unitarias pensabamos que podríamos cubrir todos los casos, pero PIT nos muestra que no es así. Vamos a ver la razón. Las lineas verdes indican que PIT ha matado a los mutantes, mientras que las lineas rojas nos indican que los mutantes han sobrevivido.
pit coverage

Al fijarnos en la lista de mutaciones, vemos que en las líneas 20 y 27 se han realizado dos mutaciones de límite condicional que no han pasado las pruebas.
pit coverage

Si nos fijamos en el código de nuestras pruebas unitarías, vemos que en el test buy_correct_quantity_of_products, no comprobamos en todos los casos que ocurriría sí:

this.pXQuantity = quantity

Vamos a añadir los casos al test a ver que nos dice PIT:

Con los cambios realizados, hemos conseguido matar a todos los mutantes, por lo que los teses han pasado.

new pit result

Conclusiones

Las pruebas unitarías tienen una gran importancía en nuestros desarrollos, si las apoyamos con pruebas de mutación conseguimos garantizar una mayor cobertura.

Con las pruebas de mutación no solo nos curamos en salud de que nuestro código funcione correctamente, si no que comprobamos que nuestras pruebas den la cobertura que esperamos.

Se escucha poco hablar sobre el “mutation testing”, pero bajo mi opinión es un buena práctica el meterlo en nuestra estrategía de calidad, ya que es un arma más para mejorar el alcance de nuestras pruebas.

Bibliografía

  1. http://pitest.org/
  2. https://www.computer.org/csdl/mags/co/1978/04/01646911.pdf
  3. http://antares.sip.ucm.es/tarot09/index_files/MutationTestingTAROT09.pdf

Geb best practices, el antes y el después

foto3En la Greach (conferencia sobre groovy y grails de España) pude asistir a una charla títulada “Geb best practices” dada por Marcin Erdmann. En el post me gustaría tratar varios temas de los que se habló, compararlo a como desarrollo yo los proyectos a día de hoy, aplicar las buenas prácticas y ver el resultado.

Antes de pasar a la práctica tengo que decir que los proyectos que hablo en el “antes” estaban realizados sobre Geb 0.13.0 y ahora sobre 1.1.

Pódeis descargaros el proyecto de ejemplo desde mi repositorio de github. En el proyecto podéis ver todos los ejemplos que tratamos en el post.

Uso del “at checks”

foto3En Geb trabajamos con el page object pattern. El at check” es una “closure” definida a nivel de page que nos permite comprobar que nos encontramos en la página adecuada. ¿Cómo? Pues básicamente dentro del “at check” podemos realizar comprobaciones a nivel de página.

 

 

 

Antes

En algunos de mis proyectos uso el “at check” en cada cambio de página, es decir, cada vez que navegaba a otra página con el “to page” volvía a hacer un “at page” pensando que era necesario para realizar un cambio de contexto de page. En muchos casos el “at checker” únicamente devolvía true, por lo que aunque no se encontrará en la página siempre iba a ser correcto.

Después

Uso el “at check” únicamente para comprobar que me encuentro en la página que quiero estar. No lo útilizo como “cambio de contexto” de página ya que no es necesario si hago el “to page”.

Conclusión

En mi caso, haría un uso responsable de “at check”, es decir, no es necesario comprobar todas las páginas, si vamos a realizar acciones en la página y no nos encontramos en ella, nos va a saltar que no se encuentra el componente con el que queremos interactuar. Pero por otro lado, si hacemos uso del “at” vamos a saber que falla porque no se encuentra en la página o la página no ha cargado, y no porque el componente no se encuentra.

En el ejemplo podeís ver que yo lo he usado ya que testeo la navegación entre páginas.

Navegar a páginas

foto1

Lo común es navegar entre páginas y realizar las acciones y las comprobaciones que sean necesarias en nuestras pruebas funcionales. No siempre la url de un página es estática (ej.: /author/), si no que en muchas ocasiones necesitamos navegar a una url compuesta por elementos dinámicos (ej. /author/[ID]).

 

 

Antes

Para poder generar las url dinámicamente en base a los datos de entrada de un test hoy en día hago uso del “builder pattern”.

Sin entrar al detalle de como se contrulle un builder en groovy y explicandolo de una forma sencilla, únicamente paso los datos necesarios para construir la url al builder y una vez generada la seteo a la propiedad url de la página. Posteriormente navego a la página.
Después

Marcin comentó en la charla dos formas de poder navegar a una página construyendo la url dinámicamente, veamoslas.

La primera es con el uso del método de página “convertToPath“. El método puede recibir los parámetros que sean necesarios para construir la url.

Cuando hagamos uso del “to Page” añadirá el path construido por el método a la url estática de la página.

El otro ejemplo es el uso del método de página “getPageUrl”. Un poco más complejo. En este caso es necesario instanciar la página con los parámetros que espera la url.

Debemos hacer el “to Page” pasandole la nueva instancia de la página con los parámetros necesarios de la url en el constructor.

Conclusión

El hacer uso de un builder hace que tengamos que gestionar la lógica de la construcción de la url para cada página, y si no son muy “estandares” no será una tarea fácil.

Por otro lado hacer uso de “convertToPath” y “getPageUrl” nos permitirá mantener la responsabilidad de la construcción de la url en la propia página.

“Trackear” las páginas

foto3

Cuando hablamos de “trackear” las páginas, hablamos de capturarlas en una variable y utilizarlar sus elementos. Esto nos va aportar ciertas ventajas que vamos a ver con los ejemplos.

Antes

En todos mis proyectos lo común es navegar a la página en la que quiero e interactuar con los elementos directamente, esto hace que si la funcionalidad requiere de mucha interacción llegue un momento en que me pierda y no sepa si estoy interactuando con el elemento de una página u otro. Lo cierto es que puede llegar a ser poco legible.

El caso que presento no es el mejor ya que interactuamos con pocos elementos, pero imaginaos que hay más elementos e incluso elementos de otras páginas con el mismo nombre.

Después

Una buena practica es “capturar” la página y referenciar a los elementos a través de la variable. Veamos.

Conclusión

Capturar la página nos va a ayudar con la legibilidad de nuestros teses. En pruebas pequeñas igual no lo vemos algo muy útil, pero lo cierto es que es de gran ayuda.

Módulos

foto2

El uso de módulos nos permite reutilizar la definición del contenido de las páginas a través de varías páginas. Pero nos aportan mucho más valoro ocultar estructuras de componentes o interacciones complejas de los teses.

 

 

Antes / Después

En mi caso no hacía mucho uso de los módulos debido a que pensaba que los módulos únicamente servian para reutilizar contenido de las páginas, y no he tenído muchas ocasiones de utilizarlo. Pero saber que nos aportan mucho más que la reutilización aporta mayor valor.

Veamos un ejemplo de uso los módulos.

Por supuesto en la charla se tocaron más conceptos y buenas prácticas que comentaremos más adelante.

 

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 web en dispositivos móviles mediante Appium

Introducción

Hoy en día muchos “sites” reciben más visitas por parte de dispositivos móviles. Esto ha hecho que las empresas quieran invertir cada vez más esfuerzos en “testear” sus aplicaciones web en sistemas operativos móviles (android, ios, windows etc.).

Podemos testear nuestras aplicaciones móviles en dispositivos reales o virtuales, cada una de las opciones con sus ventajas y desventajas:

Testing en dispositivos reales

  • Su mayor ventaja es que las pruebas van a ser reales y objetivas ya que estamos probando contra el dispositivo real.
  • Su mayor desventaja es que es necesario de grandes recursos económicos, dado que necesitamos tener más de un dispositivo con diferentes versiones de SO para garantizar una buena cobertura. Normalmente se hace un estudio previo de los dispositivos y SO más utilizados o los que más acceden a nuestra aplicación. Por no decir que gestionar una “granja” de dispositivos móviles no es fácil y es caro.

Testing en dispositivos virtuales

  • Su mayor ventaja se basa en la capacidad de crear diferentes imágenes de dispositivos y SO de una forma rápida y barata.
  • Su desventaja es que las pruebas no son del todo reales y objetivas debido a que no lo lanzamos contra un dispositivo real.

Aún así en mi caso prefiero realizar pruebas sobre dispositivos reales, y es lo que haremos en este post.

Amazon AWS

Pero antes de meternos a diseñar la arquitectura de nuestra “mini granja” y desarollar las pruebas, es necesario comentar que si vuestra empresa tiene recursos económicos suficientes, Amazon dispone de una granja de dispositivos móviles en los que podréis hacer pruebas.

https://aws.amazon.com/blogs/aws/aws-device-farm-test-mobile-apps-on-real-devices/

Arquitectura del entorno de pruebas

Antes de empezar a desarrollar el proyecto de pruebas tenemos que pensar en como queremos que sea nuestro entorno de pruebas. Para eso debemos tener en cuenta nuestras necesidades.

La arquitectura de ejemplo que planteo se basa en los siguientes componentes:

Appium arquitectura

  • Servidor Appium para SO Android: Servidor en el que se encontraran conectados los dispositivos android en los que se lanzaran las pruebas. Más información sobre Appium.
  • Servidor Appium para SO iOS: Servidor en el que se encontrarán conectados los dispositivos iOS en los que se lanzaran las pruebas. Para lanzar las pruebas en SO iOS son necesarias las librerias OS X por lo que necesitaremos un entorno MacOS.
  • Repositorio Git: Repositorio donde se encuentran los proyectos de testing. En mi caso uso GitHub.
  • Jenkins: Jenkins se encargará de lanzar las tareas de testing. Descargará los proyectos del repositorio Git, los ejecutará y obtendrá los resultados.  Más información sobre Jenkins.

La arquitectura planteada nos permitirá de forma sencilla escalar el número de dispositivos. Si no queremos montar una aplicación para gestionar la ejecución de las pruebas, Jenkins puede sernos de gran ayuda, ya que mediante sus plugin nos permitirá ejecutar los proyectos y obtener los resultados.

Levantando los servidores Appium

En este post no vamos a hablar de como montar la arquitectura planteada, pero si vamos a tratar de levantar un servidor de appium para dispositivos android, en el que poder ejecutar nuestras pruebas.

Lo cierto es que la instalación de appium es muy sencilla, en mi caso haré la instalación en un Ubuntu:
Distributor ID: Ubuntu
Description: Ubuntu 16.04.1 LTS
Release: 16.04
Codename: xenial

  1. Debemos tener instalado node ya podemos isntalar appium:
    # npm install -g appium
    # npm install wd
  2. Para levantar el servidor de appium únicamente hay que ejecutar el comando: appium
    Ojo, la última versión de appium contiene una versión de chromedriver incompatible con la última versión de chrome por lo que es necesario descargar a nuestro servidor la última versión de chromedriver y ejecutar appium con ese driver, mediante:
    # appium --chromedriver-executable [PATH CHROMEDRIVER]

Ya tendremos listo nuestro servidor de appium y a la escucha de peticiones.

Preparar y conectar el entorno Android

 

  1. Para poder lanzar las pruebas y conectar el dispositivo es necesario que tengamos instalado Android SDK en el servidor. En mi caso he descargado únicamente las librerías SDK, pero podéis acceder a la página oficial y descargaros android studio. En cualquier caso en internet hay una cantidad importante de blogs de como instalarlo.
  2. Una vez que lo tengamos instalado podemos conectar nuestros dispositivos físicos al servidor. Únicamente hay que conectarlos vía USB y ejecutar el siguiente comando, que levantará el servicio:
    # adb devices
    List of devices attached
    * daemon not running. starting it now on port 5037 *
    * daemon started successfully *
    XXXXXXXXXX device

Construyendo nuestro proyecto de pruebas

Mediante los pasos anteriores ya hemos levantado el servidor appium y conectado los dispositivos sobre los que lanzaremos las pruebas. El siguiente paso es crear el proyecto de pruebas.

github

 

En el siguiente enlace podéis descargar el proyecto de pruebas de GitHub. Es importante recalcar que el proyecto está desarrollado en Groovy haciendo uso del framework de testing Spock.

 

Vamos a comentar las partes más importantes del proyecto:

Configuración de entornos en environment.groovy

En el fichero environment.groovy vamos a definir los datos de los diferentes dispositivos que van a formar parte de nuestra granja de dispositivos:

  • Device: El nombre del dispositivo. Cuando hemos lanzado el comando “adb devices” nos ha listado los diferentes dispositivos conectados.
  • Platform: La plataforma en la que se lanzan las pruebas. En el siguiente enlace podéis ver todas las plataformas permitidas.
  • Browser: Navegador sobre el que se van a lanzar las pruebas. En el siguiente enlace podéis ver el listado de navegadores permitidos.
  • Server: URL del servidor appium. En este caso son servidores locales, pero si fueran remotos sería la URL remota. He añadido el parámetro dependiente de cada dispositivo debido a que cada dispositivo puede estar en un servidor diferente, no necesariamente en el mismo. Vamos a tomar los dispositivos como entornos independientes.

Configuración de entornos en Build.gradle

Una de las características más importantes que hemos definido en nuestra arquitectura es la disposición de diferentes dispositivos sobre lo que lanzar las pruebas. Para ello tenemos que dotar al proyecto de la capacidad de ejecutar las pruebas en cualquier entorno.

Para ello en el fichero build.gradle vamos a añadir las siguientes lineas:

  1. Mediante el método loadConfiguration obtendremos los datos del dispositivo donde ejecutaremos la prueba del fichero, que se encuentran definidos en el fichero environment.groovy.
  2. Una vez que se hayan obtenido los datos, estos se reemplazarán en el fichero test/groovy/resources/device.properties mediante la tarea processTestResources, que reemplazará los valores de los tokens definidos entre dos @.
    Podéis seguir el siguiente enlace para más información de como hacer uso de replaceTokens.

Clases de prueba (Specs)

En la fase de configuración del test (setup) vamos a obtener las propiedades del fichero device.properties para crear el objeto capabilities, donde se definen los datos del dispositivo en que ejecutaremos las pruebas. WebDriver será el objeto encargado de mandar los comandos o las ordenes al servidor de appium.

Eliminaremos la instancia del driver cuando se finalice la prueba en el paso cleanup().

Por lo que al test respecta, en este caso un test muy sencillo, únicamente hay que hacer uso del driver de selenium.

 

Ejecutando las pruebas

Para ejecutar las pruebas vamos a tener que cambiar un poco el flujo de trabajo de las tareas de gradle. Normalmente cuando ejecutamos un test de un proyecto gradle lo ejecutamos de la siguiente manera:

# gradle test

Ahora vamos a tener que ejecutar otras tareas con anterioridad para que genere el build del proyecto con los datos del entorno sobre el que queremos ejecutar las pruebas:

# gradle clean build -Penv=android1 test

  1. clean: Limpiaremos el directorio build para que genere uno nuevo.
  2. build -Penv=android1:  Environment definido en el fichero environment.groovy. De donde cogerá los datos para generar el build.
  3. test: Lanzar los teses del proyecto.

Conclusiones

Cada vez es mayor la gente que accede a nuestras aplicaciones webs mediante dispositivos móviles, por lo que cada vez tiene que ser mayor la tendencia de implementar pruebas móviles.

Si bien es cierto que lanzar pruebas sobre dispositivos reales es costoso, creo que es necesario invertir dinero y esfuerzos dado que las pruebas sobre dispositivos virtuales no nos van a dar la misma seguridad al no ser del todo reales.

Como hemos visto en el post, podemos montar una infraestructura de pruebas sencilla con varios dispositivos. Aún así si lo que queremos es disponer de una granja de dispositivos y despreocuparnos de la gestión de la misma, siempre podemos mirar algún servicio como el que ofrece Amazon AWS.

Referencias

  1. http://appium.io/
  2. https://docs.gradle.org/current/userguide/working_with_files.html#filterOnCopy
  3. https://chrislormor.wordpress.com/2013/10/27/customizing-gradle-builds-for-different-environments/

Creando un histórico de los resultados de Geb – Parte II

Introducción

En el post anterior Creando un histórico de los resultados de Geb – Parte I, vimos de donde se encontraban los resultados de las pruebas lanzadas con Geb y que datos podían ser interesantes de extraer para crear nuestro histórico.

En este caso vamos a tratar de hablar de como extraerlos y almacenarlos para poder explotarlos en un futuro.

Planificación de la extracción de datos

Si nos encontramos en un entorno de CI en el que disponemos de proyectos de automatización, es posible que lancemos las pruebas de forma planificada. Por ello lo más interesante sería poder lanzar el extractor de resultados una vez se hayan ejecutado las pruebas.

Ejemplo:

extractor

Disponemos de un servidor Jenkins en el que la ejecución de las pruebas funcionales se encuentran planificadas (Job 1).

  1. A la hora planificada se ejecuta el job 1, lanzando las pruebas funcionales.
  2. Cuando termine la ejecución de las pruebas funcionales el Job 1 llamará al Job 2, el extractor de datos.
  3. El extractor obtendrá los datos del fichero de resultados de las pruebas ejecutadas por el job 1.
  4. El extractor insertará los datos obtenidos en la base de datos.

Extractor de datos

El proyecto lo he desarrollado en Groovy, pero es posible desarrollarlo en otros lenguajes con las librerías adecuadas.

Como podemos ver en el método, extraer los datos del xml de resultados es muy sencillo.

  1. Mediante XmlParser procesaremos el fichero xml de resultados, con lo que podremos acceder a sus datos fácilmente:
    def results = new XmlParser().parse(url)
  2. Iteramos por cada test suite de tal forma que tenemos acceso a los datos del mismo. Es importante recalcar que para obtener el valor de un atributo utilizaremos @[atributo].
    results.testsuite.each { testsuite ->
    ...
    println "Parsing: ${testsuite.@name} "
    ...
    }
  3. Al igual que hemos iterado por cada test suite también debemos iterar por cada test case. En este caso comprobaremos la existencia de mensajes de “error” o “failures” .
    testsuite.testcase.each { testcase ->
    ...
    if(testcase.failure.size() != 0) {
    println "Status ${STATUS_KO} - Failure"
    println "Message: ${testcase.failure.@message}"
    }else if (testcase.error.size() != 0){
    println "Status ${STATUS_KO} - Error"
    println "Message: ${testcase.error.@message}"
    }
    ...
    }

Si estáis interesados en conocer mejor como procesar los xml en groovy podéis visitar el siguiente enlance:

http://groovy-lang.org/processing-xml.html

Almacenar y explotar los datos

El donde y el como depende mucho del proyecto de automatización en el que nos encontremos y de las tecnologías de las que hagamos uso.

En mi caso los datos los almaceno en una base de datos relacional, creando así el histórico. He desarrollado una aplicación web sencilla en grails donde ver el histórico de los resultados y graficar los datos.

test suite resume

test cases

Lo importante es adaptar y diseñar correctamente el esquema de la base de datos en base a los datos que queramos extraer y explotar. Una vez que vamos almacenando el histórico de datos listarlos, graficarlos etc.

Conclusiones

Crear un histórico de los resultados de las pruebas automáticas nos aporta grandes beneficios en el análisis de los mismos. Teniendo esta fuente de conocimientos podremos hacer retrospectivas para ver si hemos ido ganando cobertura, si tenemos menos falsos positivos etc.

Lo principal es conocer la fuente de los datos y analizar los datos que nos aportan y los que nos interesa para nuestra finalidad. Buscar la mejor forma de procesar los datos y almacenarlos en una base de datos que podamos explotar puede ser la parte más compleja.

Enlaces a la serie de post

  1. Creando un histórico de los resultados de Geb – Parte I

Histórico de los resultados de Geb – Parte I

Introducción

Cuando se lanzan pruebas automáticas uno de los pasos posteriores a la ejecución suele ser el análisis de los resultados. Es importante comprobar que las pruebas se han lanzado correctamente y verificar la veracidad de los resultados tanto positivos como negativos detectando falsos positivos. Es por ello que mantener un histórico de los resultados puede llegar a ser muy interesante y nos ofrece grandes ventajas:

  • Comparativa frente a resultados anteriores
  • Detección de falsos positivos en base al histórico de los mismos
  • Ver la evolución de las pruebas
  • Ver el crecimiento de cobertura

Se pueden listar un mayor numero de ventajas que se nos puedan ocurrir, pero lo más importante es que mantener un histórico nos va a proporcionar una fuente de conocimiento de las ejecuciones que podremos consultar en un futuro.

Como he escrito en el título, vamos a extraer los datos de los resultados de Geb, que nos permite realizar pruebas funcionales integrándose en nuestro caso con Spock.

Vamos a ello.

Resultados de ejecución

Al ejecutar las pruebas los resultados de la ejecución por defecto se almacenan en el directorio:

[project]/target/test-reports/

Vamos a encontrarnos con los siguientes ficheros:

TESTS-TestSuites.xml: Fichero con los resultados de la ejecución del test suite por completo

TEST-functional-spock-[Spec].xml: Ficheros con los resultados de las ejecuciones de los Spec. Uno por cada spec que esté definido.

Html: Directorio donde se encuentran los resultados en formato html

En nuestro caso vamos a extraer los datos del fichero TESTS-TestSuites.xml.

Análisis para extracción del histórico de resultados

Hemos lanzado las pruebas del proyecto que vimos en el post https://qajungle.com/restful-api-functional-testing/ . El fichero de resultados, en este caso con resultados positivos:

De estos resultados se puede ir observando que disponemos de varios datos interesantes para extraer, vamos a categorizarlos como datos informativos y mediciones.

Datos test suites

Dentro del fichero de resultados se listan los test suites que han sido ejecutados, en este caso solo uno, veamos que datos interesantes nos aportan:

testsuite errors="0" failures="0" hostname="aritzaguila.local" id="0" name="PostsAPISpec" package="" tests="1" time="0.912" timestamp="2016-06-21T09:42:17"

Datos informativos
Los datos informativos nos van a servir para identificar correctamente la prueba que se ha ejecutado.

  • Hostname: Nombre del host donde que se ha ejecutado la prueba. Puede ser interesante siempre que ejecutemos las pruebas en diferentes máquinas o en caso de que las balanceemos.
  • Name: Nombre del Spec que se ha ejecutado.
  • Timestamp: Hora la que se ha ejecutado el Spec.

Datos medibles
Los datos medibles nos van a servir para ver, medir, comparar y evaluar los resultados de las pruebas.

  • Errors: Errores de ejecución de la prueba. Normalmente suelen ser debido a excepciones de ejecución.
  • Failures: Fallos de la prueba. Normalmente suelen ser fallos los incumplimientos de las pruebas.
  • Tests: Número de tests ejecutados correctamente.
  • Time: Tiempo de ejecución del Spec, en milisegundos.

Datos test cases

Cada test suite dispondrá de los casos de prueba que se han ejecutado, en nuestro caso uno.

testcase classname="PostsAPISpec" name="check that the API returns all posts data" time="0.909"

Datos informativos

  • Classname: Nombre de la clase donde se encuentra el test case.
  • Name: Nombre del test case.

Datos medibles

  • Time: TIempo de la ejecución del test case.
  • Name: Nombre del test case, en milisegundos.

Es muy importante recalcar que en caso de que haya habido un error o un failure en el caso de prueba se añadirá una nueva etiqueta con la información del error o el failure.

Ejemplo para error:

En el caso de un failure la etiqueta sería <failure>.

Enlaces a la serie de post

  1. Creando un histórico de los resultados de Geb – Parte II

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