Dimensiones entre pruebas aisladas e integradas. Flakiness de las pruebas

INTRODUCCIÓN

Seguimos con la serie de “Dimensiones entre pruebas aisladas e integradas”. En los post de la serie anteriores hemos hablado sobre el grado de confianza de las pruebas y del tiempo de test de las pruebas. Vimos como el tener un mayor área de impacto ayudaba a tener un mayor grado de confianza y como el tiempo de test de las técnicas de testing es un valor que puede declinar la balanza en las decisiones de nuestra estrategia.

Existe otro punto importante en el que debemos basar las decisiones de nuestra estrategia de testing. En este post hablaremos sobre el test flakiness de cada tipo de pruebas, y al igual que en los post anteriores pondré el gráfico de dimensiones comparativo de cada tipo de pruebas.

TEST FLAKINESS

Cuando hablamos de “flaky tests” hablamos de debilidad, es decir, de la capacidad de que las pruebas fallen con facilidad por razones externas a los cambios realizados. Los flaky tests son uno de los quebraderos de cabeza más grandes del mundo del testing, sobre todo del testing funcional.

La debilidad de las pruebas pueden venir por diferentes motivos, los llamaremos flaky points o puntos de debilidad: conexión, estrategía de datos de prueba, infraestructura etc.

Puntos de debilidad

¿Cuántas veces hemos comenzado a desarrollar pruebas sin poner atención en la arquitectura del proyecto o en la arquitectura de test que tenemos planteada? En mi caso, me ocurría mucho. 

La arquitectura de test que tengamos planteada en nuestra aplicación es de suma importancia a la hora de desarrollar pruebas, dado que nos marcará no solo las necesidades de testing que tenga el proyecto, si no algo igual de importante, los puntos de debilidad.

Los puntos de debilidad o flaky points son aquellas partes o áreas del código, arquitectura y de las pruebas que pueden ser propensas a fallar debido a razones externas a nuestro código o nuestros cambios.

Los puntos de debilidad pueden ser muy variados, dependiendo de las características de nuestros proyectos, pero principalmente solemos encontrarnos con:

  • Datos: Uno de los problemas más comunes si no tenemos una buena estrategia de datos es que las pruebas fallen debido al conflicto de los datos y las dependencias entre las diferentes pruebas. Esto suele ser debido a reutilizar datos, a tener un repositorio compartido etc.
  • Infraestructura: Otro fallo común por ejemplo en arquitectura en microservicios es cuando necesitamos tener todos los servicios que necesitamos desplegados y con la versión correcta. En muchas ocasiones nos encontramos que eso no es así, debido a muchos motivos, entre ellos que tengamos que competir por el mismo entorno de pruebas y exista el conflicto entre versiones. 

    Puede ocurrir también que haya parte de la infraestructura que necesitemos que no esté bien levantada o versiones de sistemas de bases de datos etc. 

    En resumen, nos podemos encontrar con un espectro muy amplio de problemas.
  • Comunicación: Los fallos de red suelen ser un problema en el que nos tengamos que enfrentar en ciertas ocasiones. Otro problema puede ser el tiempo de carga, el tiempo de respuesta, lidiar con asincronía etc.

    Imaginemos una web, pongamos que tardamos casi un segundo en obtener los datos de una consulta y popular la tabla en una vista con estos datos, puede que nuestra prueba falle debido a que intenta comprobar la existencia de los datos antes de que se obtengan. 

En la imagen vemos identificados los diferentes puntos de debilidad dentro de nuestra arquitectura, una imagen en mente nos debería ayudar a definir la estrategia de testing que buscamos. Es importante tenerlos localizados para comenzar a tomar decisiones que ayuden a paliar estos puntos.

Una de las formas de paliar y reducir los puntos de debilidad de las pruebas es aislarlas. Aislando las pruebas reducimos notablemente estos puntos ya que no necesitamos comunicarnos con los colaboradores externos, los datos de prueba deberían estar acotados únicamente a la prueba y la infraestructura se levanta ad-hoc a la prueba.

Una cosa importante a destacar es que el punto de debilidad de datos lo podemos heredar en las pruebas aisladas si no seguimos una buena estrategia de datos aislada. Puede que se minimice el impacto debido a que la base de datos no es compartida entre diferentes proyectos o equipos, pero sí que podemos llegar a tener dependencia de datos entre nuestras pruebas, manteniendo así el punto de debilidad de datos.

Comparando el flakiness entre los tipos de prueba

Bueno, ya tenemos claro lo que es el flakyness y lo que son los puntos debilidad, ahora toca medirlo junto a otras dos dimensiones:

  • Flakiness, representado por círculos con más o menos radio, en base al grado de debilidad de la prueba.
  • Ámbito (scope) de las pruebas, en el eje X. Se trata de comprobar una parte del código, una funcionalidad concreta o un E2E.
  • Etapa (stage) de las pruebas, en el eje Y. Fase en la que se lanzan las pruebas en local durante el desarrollo, en el CI, o en fases de preproducción o producción.

CONCLUSIONES

Durante toda la serie de post, hemos visto diferentes dimensiones que nos pueden ayudar a tomar la decisión de cuándo implementar o si llegar a implementar una técnica de prueba en nuestra estrategia.

Diseñar una estrategia de testing no es fácil. No se resume en tener una suite de pruebas unitarias, integración, funcionales etc. gigantesca, dado que esto nos puede llegar a perjudicar la eficiencia y la productividad del equipo. Tenemos que llegar a balancear todos los aspectos y llegar a un balance entre productividad y calidad.

Este balance lo podemos ganar a través de implementar una estrategia de testing basado en la prevención, con más técnicas aisladas para obtener un feedback temprano del estado de la calidad de nuestra aplicación. Necesitamos también tener técnicas integradas como pruebas de soporte en etapas más reactivas, de forma que nos ayuden a obtener una mejor visión de la calidad del software pero sin llegar a parar o a bloquear las releases.

Espero que la serie os haya parecido de interés y que os ayude a la hora de definir las estrategias de testing, o a entender que diseñarla no es tarea fácil.

POSTS DE LA SERIE

1/3 Dimensiones entre pruebas aisladas e integradas. Grado de confianza.

2/3 Dimensiones entre pruebas aisladas e integradas. Tiempo de test.

3/3 Dimensiones entre pruebas aisladas e integradas. Test flakiness.

Dimensiones entre pruebas aisladas e integradas. Tiempo de test.

INTRODUCCIÓN

Seguimos con la serie de “Dimensiones entre pruebas aisladas e integradas”!. En el post anterior hablábamos sobre el grado de confianza de las pruebas y cómo el tener un mayor área de impacto ayudaba a tener un mayor grado de confianza.

Para diseñar o definir nuestra estrategia de pruebas no nos podemos basar solo en el grado de confianza, es necesario tener en cuenta otras dimensiones. En este post hablaremos sobre el tiempo de test e iteración de cada tipo de pruebas, al igual que en el post anterior pondré el gráfico de dimensiones comparativo de cada tipo de pruebas.

TIEMPO DE TEST E ITERACIÓN DE LAS PRUEBAS

Antes de ver el gráfico de dimensiones, lo primero que tenemos que entender es cómo medimos el tiempo de test.

Tiempo de test

El tiempo de test es el tiempo que tarda la prueba desde que se lanza hasta que obtenemos los resultados, sencillo, ¿no?. Normalmente lo podemos medir por:

Test time = Build time + Deployment time + Test setup time + Execution time + Test clean time +  Report time.

Resumido en:

TT = BT + DT + ST + ET + CT + RT

No todos los tipos de prueba suman estos tiempos, por ejemplo las pruebas unitarias no tienen tiempo de despliegue, por lo que, DT = 0. 

Otro ejemplo son las pruebas funcionales aisladas, que al tiempo de test hay que añadir el tiempo que tardamos en levantar (EUT – Environment up time) y tumbar (EDT – Environment down time) el entorno aislado. Podríamos poner las siguientes formulas por tipo de prueba:

  • Unit test: TT = BT + ST + ET + CT + RT
  • Contract test: TT = BT + ST + ET + CT + RT.

    El ST ya contempla el tiempo que tarda en descargarse la nueva versión del contrato en caso de que haya.
  • Integrated integration test: TT = BT + ST + ET + CT + RT
  • Isolated integration test: TT = BT + EUT + ST + ET + CT + EDT + RT
  • Integrated functional test: TT = BT + DT + ST + ET + CT + RT
  • Isolated functional test: TT = BT + EUT + ST + ET + CT + EDT + RT

    En las pruebas aisladas lo normal es no desplegar la aplicación, si no levantarla como parte del entorno, por eso el tiempo de despliegue se incluye en EUT.
  • Support functional test: TT = BT + DT + ST + ET + CT + RT
  • Exploratory test: TT = BT + DT + ST + ET + CT + RT
  • Production functional test: TT = ST + ET + CT + RT

En sí estas fórmulas no nos dicen mucho, pero nos pueden servir para conocer donde se van los tiempos de nuestras pruebas. Algo curioso es que por el hecho de tener más variables en la fórmula no significa que el tipo de prueba sea más lento que otra que tenga menos variables.

Los tiempos de las pruebas aisladas contemplan EUT y EDT, que no están contempladas en las pruebas integradas, pero estas últimas son mucho más lentas. Esto es debido a que en las integradas en el tiempo de ejecución (ET) hay que contar que se pueden comunicar con una BD real y con otros colaboradores como servicios etc, llevándose la mayor parte del tiempo.

En la imagen vemos como las pruebas aisladas al conectarse en local contra un snapshot de BD y contra los stubs de los servicios, el tiempo de comunicación se reduce bastante.

La BD únicamente tendrá los datos necesarios de la prueba, y el stub del servicio simplemente nos devolverá la respuesta que esperamos, es decir, no tenemos que esperar a que se ejecute nada en los servicios externos.

Comparando el tiempo de test entre tipos de prueba

Ahora que ya entendemos en qué nos basamos para valorar el tiempo de test vamos a medirlo junto a otras dos dimensiones:

  • Tiempo de test, representado por círculos con más o menos radio, en base al tiempo total.
  • Ámbito (scope) de las pruebas, en el eje X. Se trata de comprobar una parte del código, una funcionalidad concreta o un E2E.
  • Iteraciones (iteration) de las pruebas, en el eje Y. Cada cuanto ejecutamos las pruebas, cada cambio, antes de cada commit, en el CI, en el entorno de testing, pre-producción o planificadas o disparadas por algún evento en concreto.

CONCLUSIONES

Lo más interesante de ver a este nivel, es lo unido que está el tiempo de test con el número de iteraciones. 

Para que el testing ayude a mejorar la productividad del equipo, la idea es tratar de obtener de forma rápida el estado de la aplicación tras un cambio. Por este motivo las técnicas más rápidas se intentan ejecutar en las iteraciones más tempranas del desarrollo.

En este punto es donde las técnicas de testing aisladas nos van a aportan un mayor valor, ya que nos van a permitir ejecutar en iteraciones tempranas y de forma rápida aquellas pruebas que se deberían ejecutar en etapas posteriores, con un mayor coste temporal y por lo tanto, productivo. 

Si nos basamos en una estrategia de testing preventiva, lo que nos va a interesar es ejecutar la mayor parte de las técnicas de prueba en etapas tempranas. Vamos a intentar ejecutar las pruebas por cada cambio que hagamos o antes de cada commit o merge, lo que nos va a hacer apostar por técnicas con un tiempo de test rápido y con un grado de confianza medio alto.

POSTS DE LA SERIE

1/3 Dimensiones entre pruebas aisladas e integradas. Grado de confianza.

2/3 Dimensiones entre pruebas aisladas e integradas. Tiempo de test.

3/3 Dimensiones entre pruebas aisladas e integradas. Test flakiness.