P&R – Balancear la productividad del equipo y la calidad del producto

INTRODUCCIÓN

Ya hemos hablado anteriormente en este blog sobre la estrategia Prevention & Reaction (P&R) y cómo aplicarla en el equipo con un rol como el QDev.

Ya comentamos de la importancia de que fuera una estrategia viva y adaptable, es decir, que fuéramos capaces de pivotar sin ver afectada la calidad ni la productividad del equipo.

Pero, ¿Cómo la controlamos o monitorizamos su estado?

Donde trabajo actualmente llevo unos meses intentando crear la cultura y la estrategia de calidad. Al principio hemos tenido que definir unos KPIs muy generales y monitorizar la estrategia de forma manual. 

Como comienzo para ir aplicando e iterando en la estrategia no era una mala opción, pero hemos evolucionado. Se ha conseguido que los equipos sean más autónomos aplicando la estrategia definida, por lo que ya no nos vale monitorizar así. 

Contaré mi experiencia sobre cómo aplico el de rol de Head of QA en otro post 😛

P&R se apoya mucho sobre el concepto de smart testing, por lo que tenemos que hacer una monitorización inteligente y con decisiones automáticas basadas en datos.

En esta serie de posts hablaremos de dos conceptos importantes dentro de la estrategia P&R:

  • Balancear la red de seguridad de calidad y la productividad del equipo
  • KPIs y monitorización de la estrategia

Empezaremos por el primer concepto.

¿Qué tiene que ver la productividad con la calidad?

Bajo mi punto de vista y en nuestra estrategia, mucho. P&R se basa principalmente en la prevención, y las técnicas preventivas operan en la etapa de definición y desarrollo, por lo que tienen impacto directo sobre la productividad del equipo. 

La técnicas de testing que apliquemos no solo van a ayudar a “garantizar la calidad” del producto, si no, que van a ayudar a desarrollar con más seguridad y por tanto de una forma más eficiente.

Si abogamos por una responsabilidad compartida de la calidad en el equipo, no va a existir un rol (Tester, QA, QE, Test Engineer etc.) que sea el único en aplicar las técnicas de testing o en automatizar las pruebas  y gestionar bugs.

Todo el equipo tendrá que aplicarlas y desarrollar las pruebas junto con el QDev, quien se hará cargo de la estrategia de calidad del equipo o del producto.

Muchos conocemos la discusión en la comunidad sobre externalizar el testing fuera de la empresa o incluso tener un equipo de calidad dentro y trabajar en un equipo multidisciplinar, pero de lo que no se habla tanto es de que también se externaliza el testing dentro de un equipo multidisciplinar. 

Veamos un ejemplo:

Tenemos un equipo compuesto por 1 QA vs 4 Devs, y en la que la responsabilidad del testing (salvo los code review y unit/integration testing) es del QA. Entre sus tareas está la de gestionar los bugs, diseñar y automatizar pruebas, validar las issues, hacer testing exploratorio etc.

Cuello de botella en el flujo de trabajo

La velocidad de desarrollo siempre será mayor que la del QA, generando un cuello de botella y como resultado:

  • Mala eficiencia 
  • Pérdida de calidad en el producto.
  • Mayor frustración.

Podemos decir, metemos más personas de QA y por tanto reducimos el cuello de botella, todos sabemos que la matemática no es perfecta en estos casos. 

¿Cómo balanceamos la productividad con el desarrollo de la red de seguridad?

¿Los desarrolladores tienen que hacer testing? Es una de las preguntas que pueden surgir en debate. 

Bajo mi opinión, la respuesta es, sí. Si creemos que el desarrollador tiene que hacerse cargo de la tarea desde que la comienza hasta que está en producción, tendrá que cerciorarse de que sale con calidad. 

Es decir, desarrollar la funcionalidad, diseño y desarrollo de las pruebas necesarias (unitarias, integración, funcionales, performance etc) y validar la issue etc, forma parte de la tarea y es responsabilidad del desarrollador y el code reviewer. 

Esto no quiere decir que el QDev (en este caso) no tenga que hacer testing, claro que tiene que hacerlo. Su responsabilidad recae en crear la red de seguridad, para la que desarrollará herramientas y utilidades (las pruebas son consideradas herramientas) así como liderar la estrategia y la cultura de calidad dentro del equipo.

Lógicamente esto tiene un coste en lo que la estimación de la tarea se refiere, ya no solo es desarrollar la funcionalidad, por lo que una tarea que tuviera un coste subjetivo de talla M, tendría una talla objetiva de L. 

¿Porqué hablo de coste subjetivo? Veamos:

Coste subjetivo en la estimación

Coste subjetivo en la estimación de la tarea

Lo normal es estimar cuánto se tarda en desarrollar, y en el mejor de los casos también se asume el coste del CR y el despliegue a prod. Pero el problema viene en que no asumimos el coste del testing muchas veces al no estar integrado en el trabajo del desarrollador, por lo que el coste estimado de la issue no se acercará a la realidad. 

Como vemos en la imagen, es prácticamente imposible estimar de forma objetiva si no se integra el testing en la fase de desarrollo, ya que se depende de la finalización de una tarea de la que es totalmente dependiente.

Si nos fijamos, esa dependencia causa un cuello de botella cada vez mayor, esto es debido a que el desarrollador necesita que se valide su issue, una vez ya la ha desarrollado, haya pasado el CR y haya desplegado en un entorno de pruebas. 

En caso de un feedback negativo, deberá volver a la fase de desarrollo, CR, deploy a pruebas y volver a repetir el proceso. Un coste productivo bastante alto.

Yo siempre digo que para mí, el “fracaso” no está sólo en encontrar un error o un bug en producción, si no en el entorno de pruebas, debido a que solucionarlo tiene un coste alto, menor que el de producción, claro está. 

Es por eso que creo en la necesidad de invertir en técnicas preventivas.

Coste objetivo en la estimación

Coste objetivo en la estimación de la tarea

Por el contrario, si se ve la tarea como el conjunto del desarrollo de la funcionalidad y el diseño, desarrollo y ejecución de las pruebas, a cargo del desarrollador, mejoraremos la eficiencia y nos acercaremos más al coste objetivo de la tarea.

Lo más importante de seguir esta metodología en cuanto al testing, es que vamos a tener un feedback temprano en fase de desarrollo, y no en fases posteriores.

Las pruebas dejan de ser jueces y pasan a ser la red de seguridad de calidad. Incluso los desarrolladores pueden trabajar con TDD, BDD etc. 

La probabilidad de encontrar un fallo o un bug en fases de pruebas posteriores se verá reducida ya que se habrán detectado y arreglado en fase de desarrollo, siendo el coste productivo mucho más bajo.

El o la QDev intentará equilibrar el coste productivo y la calidad mediante el desarrollo de herramientas de testing y la estrategia de calidad del equipo.

El equipo

Para poder llegar a trabajar con este modelo, es necesario que exista una cultura de calidad dentro del equipo. Los integrantes deberán tener unos conocimientos mínimos de testing.

¿Esto quiere decir, que hay contratar desarrolladores con conocimientos de testing? No necesariamente. 

El QDev tiene la responsabilidad de crear la cultura de testing, y por tanto de formar al equipo.

Cuando la responsabilidad del testing no es compartida, hemos visto cómo puede ocurrir que la productividad del equipo se vea mermada y por tanto la calidad se vea comprometida. 

Vamos a ver dos gráficos donde se muestra la tendencia del valor de calidad-productividad frente al coste de desarrollo de calidad. Es decir, cuánto cuesta invertir en desarrollo de calidad, frente al valor que nos aporta.

La línea verde muestra el coste de desarrollo de calidad del equipo.

La línea morada muestra el valor de calidad-productividad del equipo.

El primero muestra la tendencia en un equipo donde la responsabilidad no es compartida. El coste de inversión se mantendrá estático, y debido a que es probable que se formen cuellos de botella, el valor de calidad-productividad llegará un momento en el que irá descendiendo.

Coste de desarrollo de calidad vs valor productividad-calidad

El segundo muestra la tendencia en un equipo donde la responsabilidad es compartida y gestionada por un QDev. El coste de inversión será alto hasta que el equipo coja la dinámica y esté formado. A medida que aumenten las skills de calidad del equipo, el coste de desarrollo será menor, y el valor de calidad-productividad irá aumentando.

El trabajo del QDev consiste en ayudar a reducir el coste de desarrollo de calidad y una vez el equipo sea más autosuficiente, potenciar el valor de calidad-productividad.

Coste de desarrollo de calidad vs valor productividad-calidad

Conclusiones

Cuando trabajamos en la estrategia P&R es muy importante balancear la productividad del equipo y la inversión del desarrollo de calidad.

Crear una cultura y responsabilidad compartida de la calidad dentro del equipo ayuda a trabajar de forma más eficiente, pero requiere de uno skills de testing que es probable que el equipo no lo tenga en fases iniciales. 

La labor del QDev es velar por ello. Diseñar una estrategia de calidad que sirva como red de seguridad al equipo, ayudándoles a que trabajar de una forma más eficiente.

P&R es una estrategia viva y cambiante, por lo que es necesario monitorizar para ver en qué punto estamos en cada momento. Para ello será necesario definir unos KPIs que nos sirvan como punto de control.

En el siguiente capítulo veremos cuáles son los KPIs más importantes y cómo monitorizar la estrategia.

El futuro de QAJungle

Hace meses que no escribo nada, que no doy al blog el mimo que se merece. Es un poco triste, ya que el blog ha sido y es un espejo de mi evolución laboral y personal.

¿Las razones?

Una de ellas ha sido un cambio importante en cuanto a trabajo. He querido centrarme e invertir más tiempo en adaptarme al él.

Otra razón es que necesitaba dedicar más de mi tiempo libre a lo que en realidad estaba destinado.

Por último, la falta de ideas. Siempre he querido que QAJungle sea un blog donde existan artículos diferentes a lo que podamos encontrar con una búsqueda en google, innovar (está claro que con los primeros artículos no fué así…), evitar escribir por acumular o por no perder lectores.

Es en parte por estos motivos que creo que es momento de darle un pequeño cambio de sentido al blog, adaptarlo a mi nuevo contexto.

No os voy a mentir, todavía estoy intentando darle forma a su nueva dirección, pero alguna idea ya tengo.

Un saludo.

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!

 

 

 

Intelligent testing, ¿Llega una nueva era?

Introducción

A través de un tweet de Lisa Crispin, llegue a un artículo del blog de Mabl, en el que hablaban un poco sobre el concepto de “intelligent testing”.


En este artículo me gustaría repasar los cuatro principios que se tratan del Intelligent testing. Si bien es cierto que el video del blog habla sobre cómo lo aplican a través de Mabl, intentaré ser agnóstico al producto.

¿Qué intenta resolver “Intelligent testing”?

Para entender el concepto de intelligent testing, creo que es necesario conocer los “defectos” del testing convencional (vamos a llamarlo así).

En mi opinión el testing convencional estaba pensado y muy acoplado a las metodologías de desarrollo convencionales. Con la entrada de las metodologías ágiles, el testing ha sufrido ciertos cambios intentando adaptarse, pero no siempre con el éxito que debería.

Esto ha provocado que el testing llegue a ser el cuello de botella del sistema de CI/CD. Para evitarlo y poder implementar el testing dentro de las metodologías ágiles, en muchas ocasiones se ha tenido que crear estrategias de testing muy acopladas al sistema de CI/CD, lo que tampoco es del todo correcto.

Creo que la evolución del testing siempre ha estado por detrás del desarrollo, cuando debería ir de la mano. También creo que gran parte de la culpa la tenemos los testers y QA/QEs que nos hemos ido adaptando al código y a la tecnología, en vez de estar a la altura. Debemos ser más preventivos y menos reactivos. Todavía nos queda mucho por aprender.

Lo que intenta el “testing inteligente” es básicamente eso, dejar que el testing sea el cuello de botella de nuestro desarrollo, y para eso se basa en cuatro principios.

1.º principio: Adaptarse a los cambios

Uno de los grandes problemas de la automatización, son los falsos positivos. Cuando lanzamos la suite de pruebas podemos encontrarnos con una gran número de fallos que tendremos que analizar, ya que seguramente un porcentaje de los fallos sea debido a falsos positivos.

Algunos falsos positivos vienen dados por cambios en el código que hacen que nuestro test falle, sin que en realidad sea un fallo de la aplicación. Esto suele ocurrir mucho en el caso de las pruebas de UI.

En las pruebas UI, nuestro test está acoplado a los componentes con los que interactúa, lo que hace que el test sea altamente sensible. Cuando por alguna razón el xpath o el selector del componente cambia, nuestro test falla. No quiere decir que sea error de la aplicación, es un caso de falso positivo.

Tenemos que adaptar manualmente el test y volverlo a lanzar. Esto supone una inversión de tiempo alto, por lo que somos un cuello de botella.

Lo ideal sería que el framework de testing sea lo suficientemente inteligente como para identificar el problema (un cambio del selector, en el caso anterior), identificar el cambio, probarlo y hacer los cambios en el test, de las forma que las siguientes ejecuciones vayan correctamente.

Claro, esto es relativamente “fácil” en las pruebas de UI. Pero qué ocurre si realizamos otro tipo de pruebas funcionales o de performance (porque recordemos que podemos integrar las pruebas de performance dentro de nuestro CI).

En estos casos no lo veo tan claro que sea tan fácil de adaptar el test. Supongamos que realizamos pruebas funcionales de API, lanzamos las pruebas y fallan debido a un cambio de contrato. Entiendo que si tenemos los contratos definidos y actualizados o hacemos uso de contract testing, podremos adaptar nuestros tests basados en esas fuentes de conocimiento.  

En el caso de las pruebas de rendimiento, debería depender de qué tipo de pruebas o sobre que se lancen (UI, API etc.), y adaptarse.

Otra opción que se me ocurre sería disponer de un sistema inteligente que “analice” los cambios y notifique o haga los cambios pertinentes en las suites de testing, sea del tipo que sea, funcionales, performance…

2.º principio: Testing desde el Cloud

 

Otra forma de reducir el tiempo de ejecución de las pruebas es la ejecución en paralelo. Las plataformas Cloud nos facilitan este hecho, ya que podremos disponer bajo demanda o escalar el número de ejecuciones y entornos sobre los que queremos lanzar las pruebas.

En el caso de las pruebas funcionales podremos reducir considerablemente el tiempo de ejecución, debido a que dividiremos las pruebas por el número de “ejecutores”.

En el caso de las pruebas de rendimiento podremos aumentar la carga o el número de usuarios virtuales sin penalizar el gestor de carga.Al lanzarlo sobre sistemas cloud podremos beneficiarnos de los servicios de estas plataformas, como sistemas de análisis (para analizar los resultados), heurísticos etc..

Cloud testing

 

3.º principio: El output debería ser más afirmativo y resolutivo

Como he comentado en el primer principio, tras terminar las pruebas, hay una fase de análisis de resultados. En muchas ocasiones (por no decir en la gran mayoría), cuando hay fallos, los resultados no son del todo claros y nos toca invertir tiempo en ver cual es el problema.

Esta inversión de tiempo, una vez más, nos lleva a ser un cuello de botella para los resultados, y en otras ocasiones a que se nos escapen bugs, debido a la falta de atención que ponemos en su resolución.

El que las pruebas den un output con la información necesaria (ni exceso, ni defecto) y esa información llegue a ser resolutiva, es tan importante como unos buenos teses. De nada sirve tener un test perfecto, si los resultados no son lo suficientemente informativos.

Hoy en día sacamos trazas (en ocasiones excesivas) y screenshots (en ocasiones confusos), pero no es suficiente para reducir el tiempo de análisis de los resultados.

Bajo mi punto de vista, tenemos que invertir muchos más esfuerzos en mejorar este punto. La parte buena es que los sistemas heurísticos y de machine learning cada vez están más avanzados, y creo que de alguna forma podremos integrarlo con el testing.

Creo que sería interesante disponer de un output inteligente, donde en base a los resultados obtenidos, comparándolo con una fuente de conocimientos de otros resultados anteriores y los cambios realizados, podamos dar unos resultados, no solo con buena información, si no, con el problema detectado y parte de su resolución.

4.º principio: Testing integrado en el CI

Este último principio creo que es el que está más integrado. Actualmente hay pocas empresas que no tengan el testing integrado en el sistema de CI/CD. Ya son muchos sistema de CI que disponen de plugins para muchos frameworks de testing.

El problema es que si no se cumplen los otros tres principios, el testing seguirá siendo el palo en la rueda del CI.

Conclusiones

El testing inteligente es un muy buen concepto que acerca cada vez más la posibilidad de estar a la altura de la evolución del desarrollo, creo que todavía queda por evolucionar varios aspectos, pero es factible, lo que es muy buena noticia.

Parte de esa responsabilidad recae en los testers y QA/QEs que creo que necesitamos una evolución como concepto y rol. No podemos seguir anclados en el probar el software, ni en la idea idílica de “garantizar la calidad”.

La tecnología de hoy en día, el cloud, la heurística, nos permite dar un paso más adelante y ayudarnos a anteponerse a los errores, más que encontrarlos.

Model based testing mediante Graphwalker

Introducción

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

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

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

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

Model based testing (MBT)

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

model based testing example

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

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

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

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

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

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

mtb example flow

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

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

OFFLINE

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

ONLINE

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

Vamos a verlo en la práctica!

Añadir Graphwalker a nuestro proyecto

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

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

Diseñando el modelo

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

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

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

mbt purchase

Nuestro modelo se va a componer de cinco vertex:

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

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

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

mbt exa 1

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

/paymentMethod=’paypal’; validPaymentMethod=true;

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

mbt ex 2

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

[validPaymentMethod]

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

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

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

GENERANDO LA INTERFAZ DEL TEST

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

Ejecutamos el comando de maven:

mvn graphwalker:generate-test-source

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

interface

Desarrollando y ejecutando las pruebas

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

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

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

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

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

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

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

Para ejecutar las pruebas:

mvn graphwalker:test

test results

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

Conclusiones

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

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

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

e_start(reach_vertex())

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

random(time_duration())

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

Aislando nuestras pruebas con Testcontainer

Introducción

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

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

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

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

Vamos a ello!

Aislando las pruebas del proyecto

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

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

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

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

Test architecture

Añadiendo Testcontainer al proyecto

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

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

Pruebas de integración sobre MySQL Testcontainer

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

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

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

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

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

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

testcontainer results

 

Pruebas de integración sobre enconder-service Testcontainer

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

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

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

testcontainer result encoder service

Conclusiones

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

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

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

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

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

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?

Pruebas aisladas e integradas ¿Qué son?

Introducción

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

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

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

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

Qué son las pruebas integradas?

J.B Raisemberg define las pruebas integradas como:

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

tests results

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

 

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

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

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

integrated pyramid

 

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

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

 

integrated paths

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

 

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

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

complexity

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

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

Y… ¿Las aisladas?

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

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

 

 

 

 

lineal

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

 

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

Desacoplar y aislar las pruebas integradas

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

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

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

integrated_aislated

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

integrated_aislated

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

pyramid

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

 

 

 

 

 

Conclusiones

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

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

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

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