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.

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.

 

¿Un QA puede ser también developer?

Assert QA == Developer


¿Es un QA un developer?
Me gusta hablar con la gente. Me gusta compartir opiniones. Me gusta debatir. Tras varias conversaciones con compañeros de trabajo, de los que he podido obtener de una forma u otra feedback de lo que creen que es el trabajo de un QA Engineer, me he propuesto a escribir un post que ayude a romper ciertas barreras mentales en el trabajo que creemos que hace un ingeniero de calidad y ver si en realidad es un developer.

Ingeniería de la calidad del software

La realidad es que poca gente conoce lo que es la ingeniería de la calidad. No es algo conocido, no es algo que llame la atención cuando decides estudiar informática, es la realidad.

Cuando un desarrollador habla de un QA, no piensa que desarrolle, no lo trata como un desarrollador. Es más, muchos piensan que los QAs son desarrolladores frustrados, y no es cierto.

¿Porque cuesta tanto ver la ingeniería QA como una rama de la ingeniería del software, del desarrollo?

Cuando se piensa únicamente desarrollamos pruebas se está muy equivocado. La calidad del software no solo se garantiza con teses.

Desarrollamos teses, ¡por supuesto que sí! Y en muchas ocasiones la lógica que implementamos en el test puede ser más compleja que el código o la funcionabilidad que probamos. Y si… sabemos lo que son los patrones… es más, los aplicamos.

Para garantizar que el software cumple con los requisitos de calidad exigidos los QAs deben diseñar y aplicar las estrategias de calidad necesarias. Y muy seguramente en esas estrategias se implementen diferentes proyectos, desde automatización de pruebas hasta proyectos más complejos, y adivinad, muchos de ellos habrá que desarrollarlos.

Automatización de pruebas

Automatizar pruebas funcionales no siempre significa automatizar el comportamiento de una funcionalidad mediante UI testing. En muchas ocasiones se requiere hacer pruebas funcionales a nivel de API, o probar funcionalidades cross-aplicación y cross-dispotivo.

Aquí os dejo los enlaces a la presentación y explicación de cómo Uber realiza pruebas cross-aplicación y cross-dispotivo para probar sus aplicaciones mediante Octopus.

POST:
https://eng.uber.com/rescued-by-octopus/
SLIDE:
https://docs.google.com/presentation/d/1vYXhkvgLKun72Ix91LQDDWZQdcY5VOBqKVvI1Y6riYo/pub?slide=id.gd8d657045_0_0

VIDEO:
https://www.youtube.com/watch?v=p6gsssppeT0

Para mi es importante entender que un QA no es una persona dedicada únicamente a automatizar pruebas o a realizar testing. Es un ingeniero capaz de desarrollar las herramientas necesarias que ayuden a garantizar la calidad de la aplicación.

Lo que significa que un QA es un desarrollador especializado en la calidad.

Sí, somos un equipo

Una de las cosas que me dijeron era que si el que desarrollaba la tarea hacia bien su trabajo no tenía por qué meter bugs, y ya que sabía lo que estaba desarrollando y donde afectaba él podría hacer las pruebas, por lo tanto, los QAs sobraban.

Bueno, en un mundo perfecto donde el desarrollador está encantado de probar todo lo que hace, y donde el tiempo de entrega no existe, en el que nunca se equivoca, puede ser…¡pues tampoco! Que te diga coca cola o Samsung que no hacen control de calidad de sus productos… igual sus móviles explotarían.

Es necesario desarrollar estrategias y proyectos que garanticen la calidad de nuestras aplicaciones, equipar al equipo de desarrollo con las herramientas suficientes para hacer código de calidad, para agilizar su desarrollo.

Cada equipo somos piezas del desarrollo. Desarrollo, DevOps, Producto, QA etc.

Conclusiones

Antes de ser QA fuí desarrollador, y hoy en día sigo desarrollando casi al mismo nivel que antes, salvo que antes funcionalidades de una aplicación, y ahora herramientas que garantizan su calidad. ¿Qué diferencia hay?

Los QAs también somos desarrolladores, es la realidad, y primero nos lo tenemos que creer nosotros para que los demás se lo crean.

 

 

 

 

Estrategia Prevention & Reaction – Parte III

Introducción

En el segundo capitulo vimos cómo aplicábamos la estrategia Prevention & Reaction en tiempo de producción. En el último post de la serie, vamos a ver un ejemplo de organización de equipos QA que garantizan la estrategia en todas sus fases y contextos.

Equipos de trabajo

stategy teams

Para poder garantizar la calidad del software siguiendo la estrategia se crea una estructura grupal en la que cada grupo de trabajo se dedica o toma la responsabilidad de áreas concretas de la calidad del software.

La importancia de la estructura grupal planteada reside en la necesidad de cohesión de los grupos de trabajo, debido a que las tareas realizadas por un equipo son necesarias para otro.

Esto genera la posibilidad de disponer de personas multidisciplinares, es decir, personas que puedan participar en distintos grupo de trabajo a la vez, o permitir la rotación grupal.

La imagen muestra la cohesión de la que estamos hablando, así como la cobertura de cada grupo.

De forma general, se puede apreciar que el grupo strategy  abarca los demás grupos, debido a que sus decisiones afectan a todos los grupos.

Otro ejemplo es el equipo de Testing, el cual abarca las células de desarrollo dado que tiene que dar servicio y soporte de testing.

Vamos a ver la responsabilidad de cada grupo.

Equipo de estrategia

De forma general, el equipo de estrategia debe procurar mantener la estrategia y tomar decisiones importantes sobre la misma, debiendo corregir el rumbo cuando no se siga el camino previsto.

Las responsabilidades más importantes son:

  1. Diseñar la estrategia: Este equipo debe tener siempre presentes las necesidades en cuanto a la calidad del software se refiere y desarrollar una estrategia de calidad que las cubra.
  2. Definir KPIs: Los KPIs servirán para ver si la estrategia sigue el buen camino. En el caso de que los KPIs nos muestren indicios de que no se cumplen los objetivos preestablecidos, se podrán tomar decisiones con antelación para así poder pivotar la estrategia.
  3. Integrar la calidad en el CI: Hemos visto en las diferentes fases que la estrategia requiere estar presente en el flujo de la integración continua; es, precisamente, el equipo de estrategia el que deberá garantizarlo.

Equipo de datos

Durante el análisis que hemos hecho de la estrategia, hemos recalcado la importancia de obtener los datos de los resultados de las pruebas, de los datos de producción y de disponer de un sistema de predicción de bugs.

Las responsabilidades más importantes son:

  1. Obtención de datos de pruebas: Se deberán desarrollar las herramientas necesarias para obtener los datos de los resultados de las pruebas. Como hemos ido diciendo durante la serie de posts, los datos obtenidos nos servirán para alimentar la heurística del sistema de predicción de bugs, así como para mantener un histórico de resultados.
  2. Obtención de datos de producción: Los datos de producción van a servirnos para disponer de datos objetivos que nos ayuden a crear un mapa de calor de acciones, o bien como datos de entrada para las pruebas. Si nos fijamos en los logs de producción, podremos localizar trazas de errores y adelantarnos a solucionarlos o a reportarlos.
  3. Obtención de datos para KPIs: Los KPIs son indicadores o métricas que nos aportan conocimiento del estado de las acciones que estamos realizando. Mediante los KPIs seremos capaces de detectar defectos en nuestra estrategia o, por el contrario, ver el éxito de la misma. El equipo de datos debe desarrollar las herramientas necesarias para poder obtenerlos.
  4. Análisis y reporte de los datos: El análisis de los datos es de las acciones más importantes que debe realizar el equipo, dado que los resultados de los análisis son decisivos para la estrategia.

El equipo de datos tiene que trabajar estrechamente con los demás equipos de QA, debido a que va a necesitar extraer datos de todas las pruebas, así como de los resultados de la validación de las RCs o de la gestión de bugs. Estos datos serán analizados junto con el equipo de estrategia.

Equipo de automatización

El equipo de automatización deberá desarrollar los proyectos o herramientas necesarias para la ejecución de las pruebas automáticas. Los proyectos no tienen por qué ser únicamente desarrollo de pruebas automáticas; el equipo de automatización también se encargará de desarrollar los frameworks necesarios para el desarrollo de las mismas.

Las responsabilidades más importantes son:

  1. Framework de desarrollo de pruebas: Para muchos casos es necesario desarrollar, adaptar o mejorar un framework de testing. El equipo de automatización tiene que proveer ese framework a los equipos que desarrollen pruebas automáticas, ya sean ellos mismos o los desarrolladores.
  2. Desarrollo de pruebas funcionales: A pesar de que los desarrolladores implementen pruebas funcionales de sus tareas, es posible que el equipo de automatización necesite realizar unas pruebas automáticas de más alto nivel. Dentro de estas pruebas, se pueden listar también pruebas E2E, API, Mobile etc.

Es necesario que el equipo de automatización trabaje estrechamente con los departamentos de producto y de desarrollo, debido a que las pruebas que se desarrollen van a depender mucho de las conversaciones entre los diferentes departamentos.

Equipo de Rendimiento y Seguridad

El rendimiento y la seguridad de nuestra aplicación son unos de los valores más importantes que tenemos que garantizar, pero no siempre son los más sencillos. El equipo de rendimiento y seguridad se deberá encargar de desarrollar las pruebas necesarias que garanticen la calidad de ambos aspectos.

Las responsabilidades más importantes son:

  1. Pruebas de rendimiento: Disponer de una suite de pruebas de rendimiento (pruebas de carga, estrés, estabilidad, etc.).
  2. Pruebas de seguridad: A pesar de que los desarrolladores implementen pruebas funcionales de sus tareas, el equipo de automatización es posible que necesite realizar unas pruebas automáticas de más alto nivel. Dentro de estas pruebas, se pueden listar también pruebas E2E, API, Mobile etc.

El equipo de rendimiento y seguridad debe trabajar estrechamente con el departamento de sistemas o sysop, debido a que sus pruebas van a tener impacto en los entornos y en la aplicación.

Equipo de Testing

Dentro de las tareas más importantes se encuentra el validar o rechazar la RC y para ello es necesario que el equipo de testing realice las pruebas correspondientes. Por otro lado, el tratamiento de bugs y las pruebas bajo demanda también son tareas del equipo de testing.

Las responsabilidades más importantes son:

  1. Validación de RCs: Sacar la RC adelante es la finalidad de todos (desarrolladores, sys, QA, producto etc.), por lo que es la tarea más importante del equipo de testing. En caso de que en tiempo de RC en la fase de prevención todo haya ido bien, en fase de reacción los testers deberán buscar cualquier fallo crítico y, en caso de localizarlo, rechazar la RC.
  2. Pruebas bajo demanda: En ocasiones es necesario realizar pruebas bajo demanda sobre una tarea en concreto. Se suele hacer cuando la tarea es compleja o crítica. De esta forma, nos podremos asegurar que no vamos a meter fallos en la RC.
  3. Diseño de casos de prueba: Sin casos de pruebas no se sabe qué probar. El equipo de testing deberá diseñar los casos de prueba necesarios para la validación de la RC, o para el testing bajo demanda.
  4. Tratamiento de bugs: Cuando en producción se localiza un fallo o un bug es necesario reportarlo. El equipo de testing tiene que reproducirlo y aportar la información necesaria al equipo de desarrollo para que lo solucionen. Un aspecto, en mi opinión, importante y a veces olvidado es que disponer del conocimiento suficiente para entender el código de la aplicación ayudará al tester a obtener más información del error. De este modo, el desarrollador invertirá menos tiempo en solucionarlo.

El equipo de testing tendrá que trabajar estrechamente con el equipo de producto y desarrollo para obtener la información necesaria que permita preparar los casos de prueba frente a posibles bugs.

Conclusión final

Durante la serie de posts, hemos visto en qué se basa la estrategia de “Prevention & Reaction”. Es una estrategia teórica, basada en mi experiencia como QA, en lo que he aprendido estos años y en cómo hoy en día enfocaría el trabajo para garantizar la calidad en un entorno de desarrollo ágil.

La finalidad de la estrategia es garantizar la calidad desde una etapa temprana. Su base principal son los datos y el análisis de los mismos, pero también puede ser su punto débil, debido a que al comienzo de la implementación de la estrategia no disponemos de los datos suficientes. No obstante, como suele ocurrir, su punto débil también es su punto fuerte.

A medida que dispongamos de más datos, la heurística de nuestro sistema de predicción de bugs será mejor y, por lo tanto, nos ayudará a reducir el riesgo de dejar pasar fallos a producción.

Estrategia Prevention & Reaction – Parte II

Introducción

En el capitulo anterior vimos en qué se basaba la estrategia de Prevention & Reaction y cómo la aplicábamos en tiempo de RC. En esta segunda parte vamos a ver qué es el tiempo de producción y qué herramientas de calidad utilizamos para poder aplicar la estrategia Prevention & Reaction.

Tiempo de Producción

production time

Denominamos tiempo de producción al estado en que la release ha llegado a desplegarse en los entornos de producción. El software ya se encuentra disponible para que los usuarios lo utilicen, por lo que los errores que hayamos dejado pasar en tiempo de RC serán detectables por los usuarios. Las herramientas de calidad que actúan en tiempo de producción nos ayudarán a prevenir esta situación.

Prevención en tiempo de Producción

Prevention in production time
Las herramientas de prevención en tiempo de producción tratan de obtener información del estado y de las acciones de los usuarios y así poder utilizarlas en tiempo de RC. La información que obtengamos del entorno de producción nos servirá para adelantarnos a problemas aún no descubiertos por los usuarios, así como servirnos de datos para nuestras pruebas previas.

La información principal que obtendremos de producción es la siguiente:

  1. Trazas de errores: Lo que nos permitirá detectar bugs antes de que los usuarios sean conscientes, o proveernos de información para solucionar los fallos.
  2. Acciones de usuario: Hay ocasiones en las que los usuarios realizan acciones que no son contempladas por el equipo QA; con esta información podremos obtener mapas de calor de acciones y ajustar y mejorar nuestros casos de prueba.
  3. Datos de entrada: Obtener datos de entrada reales para nuestras pruebas puede ayudarnos a aumentar de alguna forma nuestra cobertura.

 

Como se puede observar, las herramientas que se plantean intentan obtener información para ayudar a las fases anteriores de la estrategia y así minimizar el riesgo de inserción de bugs en RCs futuras.

Reacción en tiempo de Producción

reaction in production
Cuando el software se encuentra en producción es probable que el usuario reporte bugs o, en el peor de los casos, que sea necesario tratar un fallo bloqueante que requiera de un hotfix. Las estrategias en tiempo de producción intentan manejar estas situaciones.

Bien es cierto que el tratamiento de bugs y el protocolo de actuación frente a la necesidad de realizar un hotfix es muy personal a la empresa, pero  lo que aquí intento mostrar es una estrategia básica del tratamiento de un caso de hotfix.

Aparte de las dos etapas clave de desarrollo de la solución y posterior fase de pruebas por parte del equipo de QA, es muy importante recabar la información necesaria para alimentar la heurística del sistema de predicción de bugs del que hablamos en el capítulo anterior, así como para hacer análisis y retrospectivas.

Conclusiones y adelanto

El capítulo aporta puntos bastante importantes en cuanto a las estrategias que seguimos en el tiempo de producción. Cuando la aplicación se encuentra en producción, los usuarios se toparán con los errores a no ser que podamos prevenirlos mediante la información que vamos obteniendo a lo largo del proceso.

Debemos disponer de un protocolo para tratar los errores bloqueantes: el protocolo debe ser lo suficientemente eficaz para que el impacto, tanto para el usuario como para el negocio, sea mínimo. Esto se traduce en un protocolo que nos aporte garantías en cuanto a la validez de la solución, así como rapidez en el desarrollo de la misma. Estos errores nos deben ser de ayuda para prevenir errores futuros.

La mayor fuerza estratégica de Prevention & Reaction la encontramos en tiempo de RC, ya que un error en producción es una muestra de que la estrategia no se está aplicando correctamente. No obstante, no debemos darle menos importancia al tiempo en producción ya que nos aporta una gran información con la que podremos mejorar nuestras pruebas.

En el próximo y último capitulo veremos como aplicar la estrategia mediante grupos de trabajo.

Estrategia Prevention & Reaction – Parte I

Introducción

Con las metodologías ágiles, el software se encuentra en un desarrollo continuo, de una forma iterativa e incremental, de forma que la mayoría de empresas despliegan diariamente una o más versiones de su aplicación.

Este hecho provoca que las estrategias de aseguramiento de la calidad (de ahora en adelante QA) deban adaptarse, dado que únicamente cubrían las necesidades de las metodologías tradicionales.

Actualmente existen empresas que implementan metodologías ágiles sin la necesidad de perfiles de calidad, siendo los desarrolladores quienes la aplican, mediante el desarrollo de pruebas unitarias y de integración. Esto es debido a que los equipos de calidad pueden demorar o ser un bloqueo en dichas iteraciones de desarrollo.

Tras en análisis anterior se me ocurrió como trabajo personal desarrollar una estrategia que aplicara esa idea. La estrategia “prevención y reacción” pretende adaptarse a las metodologías y equipos ágiles garantizando la calidad del software, tanto en fases de desarrollo como en fases posteriores y puesta en producción.

Contextos de prevención y reacción

El término de prevención se refiere a evitar la inserción de bugs en el desarrollo; por el contrario, el término de reacción se refiere a la actuación de localizar los fallos y al tratamiento en caso de hacerlo.

Diferenciamos dos contextos o tiempos diferentes en los que operan las estrategias tanto de prevención como las de reacción: Tiempo de la release candidata (en ahora en adelante RC) y tiempo de producción.

Tiempo de RC

rc_time_contexts

Llamamos tiempo de RC al estado en la que la release no ha sido desplegada en producción. Comienza en la toma de requisitos del desarrollo hasta la fase de pruebas de la RC.

Prevención en tiempo de RC

strategies
Las estrategias de prevención en tiempo de RC tratan de evitar la inserción de bugs o errores en la RC, de tal forma que la RC llegue a la fase de pruebas lo menos impactada posible.

Las estrategias operan fundamentalmente en tres fases del desarrollo de la RC:

  • Fase de diseño: En la fase de diseño, el equipo de calidad debe obtener información de los criterios de aceptación por parte del equipo de producto. Se podría obtener en fase de requerimientos pero es probable que en la fase de diseño estén más definidos, por lo que aquella (la fase de requerimientos) sigue siendo una etapa temprana.
  • Fase de desarrollo: Implementación de un sistema de predicción de bugs. Nos garantizará una información objetiva de los riesgos de los desarrollos realizados en la RC, puntos conflictivos y propensos a fallo. Mediante un sistema de predicción se pueden focalizar las pruebas con mayor precisión y así reducir el tiempo de ejecución de las mismas.
  • Fase de pruebas: Antes de generar la RC (la cual se enviará a pruebas finales) es necesario que pase  pruebas de análisis estático, unitarias, integración y funcionales. El equipo de desarrollo será el encargado de desarrollarlas. La petición finalizará  una vez esté el desarrollo junto con las pruebas. Se realizarán pruebas bajo demanda por parte del equipo de calidad. Todos los resultados se analizarán y se utilizarán para el sistema de prevención y futuras retrospectivas.

Aplicación estrategias

La imagen muestra una forma de aplicar las estrategias de prevención en tiempo de RC.

  1. Se desarrolla la petición junto con las pruebas por parte del equipo de desarrollo. El sistema de predicción obtiene los datos de los cambios realizados.
  2. Se ejecutan las pruebas desarrolladas unitarias y de integración. Los resultados se almacenan para un posterior análisis. Esos datos servirán para alimentar la heurística del sistema de predicción de bugs.
  3. Se despliega la petición en un nuevo entorno.
  4. Se ejecutan las pruebas funcionales. Los resultados se almacenan tanto para el análisis como para el sistema de predicción.

Reacción en tiempo de RC

Reaction in RC time

Las estrategias de reacción en tiempo de RC tratan principalmente de localizar los errores que se nos han “pasado” en la fase de prevención. Dado que la RC ya ha sido generada, los errores ya se encuentran ella, por lo que es tiempo de reaccionar y localizarlos.

Es bien cierto que en esta fase se implementan estrategias o acciones que garantizan la calidad de la aplicación que en la fase anterior no se podrían aplicar.

Como se puede ver, la intención no es solo asegurar la calidad desde un aspecto funcional, si no que intentamos realizar pruebas que garanticen el rendimiento y la seguridad de la aplicación.

reacción rc

La imagen muestra una forma de aplicar las estrategias de reacción en tiempo de RC.

  1. Se despliega la RC en un entorno de pruebas.
  2. Se ejecutan las pruebas de calidad. (Funcionales, UAT, security, performance…) Los resultados se almacenan para un análisis posterior y como heurística del sistema de predicción de bug.
  3. Se acepta o rechaza la RC.

La fase de reacción en tiempo de RC es la última antes del despliegue en producción, lo que significa que localizar un bug en producción que se ha insertado en la RC es un caso de fracaso.

Conclusiones y adelanto

Hemos visto cómo aplicar la calidad del software en tiempo de RC en dos contextos diferentes: Preventivo y reactivo.

Entender los dos contextos nos ayudará a conocer las diferentes estrategias que aplicamos y saber el porqué. Garantizaremos la calidad del software desde etapas tempranas y focalizaremos la mayoría de nuestros esfuerzos a generar RCs validas. De esta forma ahorraremos tiempos en el testing, dado que centralizamos las pruebas y en el desarrollo, dado que minimizamos la aparición de bugs.

En esta fase es muy importante la obtención de los datos. Los resultados de las pruebas sirven para realizar retrospectivas que ayuden a valorar el éxito de la aplicación de nuestra estrategia de calidad, debido a que si en la fase de pruebas no encontramos fallos que posteriormente se encuentran en producción deberemos tomar medidas. En el caso de los datos de predicción, nos ayudarán a focalizar nuestras pruebas, por lo que tendremos más opciones de encontrar posibles fallos y reduciremos nuestro tiempo de testing.

En el próximo post de la serie analizaremos los contextos de prevención y reacción en tiempo de producción. Veremos cómo los datos de producción nos pueden ser de gran ayuda para adelantarnos a fallos y cómo nos servirán para objetivizar nuestras pruebas.

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/

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