QDev, ¿Una nueva forma de aplicar el testing?

Introducción

Después de bastante tiempo sin publicar nada debido a que entre otras cosas he estado centrado en el proyecto de la comunidad de NorthemQuality, vengo con un artículo que me hace especial ilusión.

Durante estos dos últimos años, en el equipo hemos tenido que trabajar en varios proyectos que han tenido que hacer que el rol de QA/E tenga que evolucionar a uno nuevo que nosotros mismos hemos definido.

En este artículo me gustaría explicar qué es lo que ha hecho que el rol evolucione y en que ha evolucionado!

El proyecto como punto de inflexión

Todo comenzó cuando tuvimos que enfrentarnos a un proyecto en el que veíamos que íbamos a tener que lidiar contra bastantes infortunios.

El primero de ellos era que no teníamos un product owner para el proyecto por lo que, no íbamos a tener unas tareas bien definidas y por lo tanto, no íbamos a tener unos criterios de aceptación claros.

El segundo era que no había una estrategia de calidad, por lo que el testing que se hacía no era eficiente, y además era un stopper a parte de no tener unos resultados fiables.

Por otro lado, no existía el concepto de equipos multidisciplinares por lo que el testing se hacía por otros equipos que no fueran el equipo owner del proyecto.

Todo esto sumado a otros factores, hacía imaginarnos que la forma de trabajar de la que veníamos como equipo no iba a funcionar.

Nuestro equipo estaba compuesto por un Tech Lead, tres desarrolladores y un QA/E.

La evolución a QDev

Como QA/E era mi responsabilidad el que el producto a parte de responder como se requería. Antes de este proyecto, la forma en la que lo hacíamos era definiendo y desarrollando el juego de pruebas funcionales y mediante la gestión de bugs.

Pero claro… vista la situación a la que nos teníamos que enfrentar, no iba a funcionar.

El equipo siempre ha defendido el modelo estratégico “Prevention & Reaction” que concebí en su día, pero hasta el momento no lo habíamos podido poner en marcha. Igual era la hora de empezar a aplicarlo en la medida de lo posible y comprobar si era viable, había que arriesgar.

Lo primero que hicimos fue pensar como equipo en cómo queríamos trabajar el testing, que necesitábamos y en base a las necesidades, identificar cuál serían las responsabilidad del QA/QE.

Siempre he defendido que el QA/QE en los equipos multidisciplinares debe ser un desarrollador más, al igual que diferenciamos entre fronteder y backender.

De aquí nació el rol de QDev!

El nuevo rol, QDev, tiene que crear una red de seguridad de calidad (QSN) que brinde al equipo la confianza suficiente de desarrollar, para eso debía entre otras cosas debe:

  • Responsabilizarse de definir y aplicar la estrategia de calidad. En nuestro caso nos basamos en el modelo “prevention & reaction”, que va muy alineado al trabajo del QDev.
  • Definir y desarrollar el SUT (system under test) del producto.
  • Trabajar codo con codo con el product owner desde el inicio del proyecto en la definición de los criterios de aceptación. Diseñar el plan de pruebas de cada técnica de testing que posteriormente se desarrollará.
  • Desarrollar las herramientas y frameworks necesarios de testing.

Pero para esto, es muy necesario hacer un cambio de mentalidad y entender que:

  • El QDev es un desarrollador más, especializado en el desarrollo de la estrategia y arquitectura de calidad. Debe hacer partícipe al equipo en las decisiones que se tomen al respecto, siempre de forma consensuada.
  • Todo el equipo es responsable de la calidad del producto. Por ejemplo puede ocurrir que la gestión de los bugs se haga entre todo el equipo.
  • El QDev no es un juez, no dictamina si algo está bien o mal, el es el responsable de desarrollar la QSN que lo hace.
  • Las pruebas son una herramienta más, no son el objetivo.

Ejemplo de trabajo de QDev

Para poder entender mejor el concepto de cómo debería trabajar o cómo debería ser un QDev, vamos a ver un ejemplo.

Imaginemos que el equipo está compuesto por un Tech Lead, un Product owner y varios desarrolladores, entre ellos backenders, frontenders y un QDev (o más).

Desde el comienzo todos deberían participar en la definición del producto y en su arquitectura. El QDev en este punto debería comenzar a definir la estrategia de calidad:

  • Pensar en las necesidades de la QSN. La idea principal es que el equipo trabaje sobre una QSN existente u operativa.
  • Empezar a definir a alto nivel la arquitectura del SUT (system under test).

Supongamos que de ese trabajo, el QDev ha diseñado la siguiente estrategia que apoya su idea del QSN. Lógicamente esto es solo un ejemplo, muy discutible y dependiente del contexto del producto o proyecto.

De esta estrategia lo importante es entender que el QDev va a trabajar desde el comienzo del producto. Trabajará sobre las diferentes técnicas de testing que haya definido en las diferentes fases.

Siguiendo esta estrategia, el QDev no solo deberá centrarse en diseñar las pruebas e integrar las diferentes técnicas de testing, si no que, necesitará desarrollar el SUT aislado (mocks, contenedores etc) para que el equipo ejecute las pruebas de forma aislada. Incluso desarrollará herramientas para facilitar el trabajo del equipo.

Otro punto importante es no olvidarse de la importancia de seguir con el testing en la fase de producción.

Conclusiones

El evolucionar el rol de QA a QDev y comenzar a hacer uso del modelo “Prevention & Reaction” (al menos una pincelada) en nuestro caso fue un caso de éxito, pero todo depende del contexto del producto y de la madurez del equipo.

En mi caso fue muy importante la cultura del equipo y cómo entendíamos la calidad como una responsabilidad de todos. Es más, había tareas de QA que estaban diluidas en el equipo, yo entendía perfectamente que ellos harían pruebas funcionales, ya me encargaría yo de hacer el code review. Algo muy interesante es que también hacíamos pair testing.

Lo más importante es entender que el QDev, es el encargado de la QSN del producto o del proyecto. Para ello deberá tener los conocimientos suficientes para desarrollar y aplicar la estrategia de calidad, así como el SUT.

Ser el encargado no significa que sea el único responsable, como he comentado, la responsabilidad debe ser compartida por todo el equipo, por lo que es muy importante que todos participen en aplicarla.

El éxito depende de la unión del equipo, no del éxito de cada individuo.

Probe testing, the pathfinder

Introduction

A few days ago I was talking with a co-worker about a problem that his team is having.

The case is, this team consumes some APIs which contracts are changed without notifying the team and without any backward compatibility so, in the worst case, they get to the situation of having errors in production.

In an ideal world, the problem would be solved by applying contract testing or the API provider notifying about the changes, and developing always with backward compatibility, but what can we do when this is not happening?

Probe testing

We talked about the concept of using tests in the form of a probe or a pathfinder, in such a way that the tests inform us of any change in the API. In this case, I could think of several ways to probe:

Keep a scheduled test’s run, in such a way that when tests fail, we know that the contract has changed. The problem is that, as it is scheduled until it is executed and the probe detects the change, the problem could already be in production.

probe01

Keep the tests active, that is, have them running continuously. The problem that I see is that we would have a very extensive report and we could lose the real visibility of the status of the probe target. If for example, we add notifications, which would be the most logical thing to do, having a communication failure with the API we may start receiving false positive notifications. We would also have to handle the notifications.

probe02Have a listener. Imagine that changes are made in the repository of the API that we consume, if we have a service listening to this and we see or we are notified of a change, the probe will be launched to check the changes. Logically we should have view access to the repository, which is the case here, as it is part of the same company, or being a public repository.

probe03

The tests should be minimal and as simple as possible, focused on the problem, trying to avoid false positives. As a probe, we should not waste much time or resources in the maintenance. It would also be interesting to manage the probes through a dashboard: to see results, activate or deactivate them, etc.

Another interesting idea is that if we have the knowledge and resources enough, we can develop more intelligent probes, which manage their notifications system, execute, etc.

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!

 

 

 

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.

Testing with sewage treatment plant

Imagine that an issue in development is like water that has to be purified, and our testing architecture or techniques are the purification machine.

If we do not purify or filter, the water will come with a big amount of waste. In order to have clean water to consume, it must go through a process of “filtering” step by step.

filter

The same happens with the issue that we are developing. If we do not apply testing techniques and clean code, we will obtain a product with many residues (bugs, corner cases, dirty code, etc).

But we have to apply them from an early stage. So it is not correct doing testing once the issue is developed. The testing starts from the very beginning, defining the acceptance criterias, applying the different testing techniques during the development process.

In this way we will detect the “imperfections” in advance, and the delivery and deployment will be safer and faster avoiding the testing bottleneck.

A bit of dirty water can contaminate the entire pool in production, after that, ¿How could you find the drop that spoiled it all?

Charlando de testing con: Federico Toledo

Introducción

Si hay algo que me gusta y de lo que creo que aprendo es de hablar con los compañeros sobre el mundo del desarrollo y el testing. La mayor parte del conocimiento que tengo hoy en día es gracias a leer artículos y a discutir ideas.

Como creo que es mejor compartir, he decidido crear una nueva serie en el blog en la que colgaré “entrevistas” o “momentos de charla” sobre testing con otros compañeros. Mi idea es hablar sobre testing no solo con testers o QEs, si no con todos los roles implicados en el mundo del desarrollo. Conocer diferentes opiniones y puntos de vista que puedan hacer discutir y replantearme las cosas.

En el primer post de la serie “Charlando de testing con”  lo he decidido hacer con Federico Toledo, tras hablar varías veces con el y leer su libro creo que tiene una opinión muy interesante sobre el actual estado del testing, así como grandes ideas. No me gustaría adelantaros nada así que os presento la charla con Federico Toledo:

Entrevista

 

QA Jungle – Charlando de testing con Federico Toledo from QA Jungle on Vimeo.

Comentarios del libro: Introducción a las Pruebas de Sistemas de Información

Introducción

tapa libro
Hace unos días Federico Toledo escribió un nuevo post en su blog hablando sobre el libro que escribió en el 2014  “Introducción a las Pruebas de Sistemas de Información“. Os recomiendo que os descarguéis el libro gratuito en el mismo post.

En la siguiente lectura voy a tratar de comentar y hacer crítica de lo que me gusta y lo que me gustaría encontrar en el libro. Del porqué recomiendo el libro y en mi opinión a qué tipo de lector o profesional está orientado.

¡Vamos allá!

 

 

El autor

Para mí es muy importante saber quién es el autor de un libro, sobre todo si es técnico. La razón es que muchos libros técnicos comparten el mismo contenido, hablan de los mismos temas y tratan los mismos conocimientos.

¿Entonces donde se encuentra la diferencia? En el autor. El autor aporta su experiencia, sus conocimientos y su personalidad. Eso es lo que hay que buscar en los libros técnicos. Tenemos que mirar más allá de la técnica pura y dura, tenemos que analizar la experiencia del autor sobre el tema que trata. Si no, solo se quedará en un simple manual.

A Federico Toledo le conozco de hablar personalmente alguna vez con él, de leer sus artículos y seguir sus consejos. Tiene un CV y una experiencia envidiable, pero sin entrar a lo personal, es una persona que se centra mucho en la práctica, al menos en los artículos que he leído. Por lo que antes de comenzar a leer el libro me hacía una idea de cómo se iba a enfocar.

Enfoque del libro

Uno de los mayores problemas de los libros técnicos, es la densidad a la hora de leerlos. ¡La mayoría se hacen eternos!

En mi caso ocurre por varios motivos: El autor es muy monoto, el libro no aporta mayor información que otros, no encuentro en el libro lo prometido. Introducción a las Pruebas de Sistemas de Información no ha sido el caso.

He leído varios libros y artículos de testing de diferentes autores y muchos de ellos solo han sido copias de otros, algunos ni siquiera aportaban la experiencia propia. Con lo que al final pensaba que solo aportaban humo.

En el caso de Introducción a las Pruebas de Sistemas de Informacióha sido diferente. Tenía ganas de encontrar un libro que se centrará en la práctica, que me aportara técnicas y estrategias para mejorar mis skills de testing. Bien es cierto que no profundiza demasiado en algunos de los conceptos tratados, tampoco creo que sea ese su objetivo, ya que es un libro de introducción al mundo del testing.

Temas del libro

¿El libro cubre temas suficientemente atractivos para mí? La respuesta es sí.

Cuando leemos libros de testing, nos encontramos con muchos temas sobre cómo implementar el testing en la metodología X. Al final nos encontramos que se han tratado más temas de metodologías y gestión de equipos que de estrategias o técnicas de testing.

Introducción a las Pruebas de Sistemas de Información, en mi opinión toca los temas necesarios para entender y comenzar a aplicar las estrategias y técnicas de testing. Creo que el autor ha buscado ser claro y practico con los temas, y creo que lo ha conseguido.

El que haya una temática tan clara ayuda a que también sea un libro de consulta, me explico. Muchas veces lees un libro técnico y tocas mucha información en varios temas, como si de una novela técnica se tratase. En el caso de Introducción a las Pruebas de Sistemas de Información si necesitas información sobre pruebas de rendimiento, basta con el ir al tema “Pruebas de Performance” para encontrarte toda la información necesaria, sin necesidad de bucear por todo el libro.

Lo que me gusta y lo que me gustaría encontrar


LO QUE ME GUSTA

  • Es un libro muy práctico, aportando ejercicios, lo que ayuda a entender y a interiorizar los temas que se tratan.
  • Para apoyar los ejercicios y las prácticas aporta herramientas al lector. Si de verdad te interesa, no te vas a quedar con la miel en los labios, puedes practicas e incluso utilizarlas para tu día a día en el trabajo.
  • El autor expone casos cotidianos (fuera de toda técnica) y humanos para entender los ejemplos de testing. Esto es muy positivo sobre todo para las personas que comienzan o quieren entender en el mundo del testing.
  • Repasa muchas técnicas de testing y la opinión personal respecto a cada una, con lo que aparte de aportar conocimientos técnicos, también aporta personales. ¡Lo que yo busco en un libro!
  • Aporta diferentes artículos y opiniones respecto a varios temas que trata. Creo que el autor buscar que el lector saque su propia conclusión a través de varios puntos de vista, algo muy positivo ya que ayuda a que el lector pienso por si mismo.

LO QUE ME GUSTARÍA ENCONTRAR

  • Cuando el autor habla de las técnicas de testing, echo en falta tratar la teoría de la pirámide del testing . Durante el tiempo que llevo trabajando como ingeniero de calidad me he encontrado con pirámides invertidas, lo que es signo de no aplicar y desarrollar correctamente el testing. Echo en falta que se trate este tema, ya que creo que es necesario conocerlo y entenderlo para no acabar con suites inmanejables y poco eficientes. No obstante, Federico Toledo si lo ha tratado en la versión en inglés del libro :).
  • Una vez más echo de menos que se traten temas de estrategias y técnicas de prevención. Es cierto que hoy en día el mundo del testing y el QE está orientado a garantizar la calidad mediante diferentes técnicas de pruebas (funcionales, performance, automáticas, unitarias…). Pero creo que sería interesante concienciar en acto de la prevención.
    Estás mismas técnicas pueden ser usadas para prevenir la inserción de fallos y la regresión de calidad, más que buscarlos. Poco a poco se está invirtiendo más tiempo en investigar técnicas de prevención de bugs mediante algoritmos de IA etc.
    Lo más seguro que no sea el “scope” del libro, pero suelo echar en falta algún tema o comentario al respecto, que muestre que el mundo del QE intenta expandirse y evolucionar también hacia otros aspectos.

Conclusión

Tanto si quieres comenzar o entender en el mundo del testing, como si eres un experto y quieres afianzar tus conocimientos te recomiendo la lectura del libro.

En el primer caso vas a ganar los conocimientos suficientes para entender la importancia del testing y comenzar a desarrollar tus habilidades.

En el segundo caso puede que te encuentres que el autor no profundiza, pero hay que entender que no es el “scope” del libro. A pesar de ello vas a poder descubrir y repasar técnicas muy interesantes, así como ver otros puntos de vista que seguramente van a hacer que te lleves una grata sorpresa.

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

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/