Loading Likes...

Breve historia de nuestra aplicación

Para que entendáis bien el problema que tenemos os pongo en situación: una aplicación con 9 años de desarrollo y conceptos como TDD o SOLID aprendidos demasiado tarde.

Durante los primeros 5 años éramos menos de 4 desarrolladores con lo que la deuda técnica era más o menos gestionable. Sin embargo en los últimos años hemos multiplicado el equipo por 4 y no hemos sabido cambiar la forma de hacer las cosas hasta demasiado tarde. Los nuevos desarrolladores que entraban al equipo adquirían los malos vicios que llevábamos arrastrando desde el principio. El alto grado de acoplamiento entre componentes hacía que tocar algo en un sitio pudiera hacer que algo se rompiera en sitios aparentemente no conectados. En seguida vimos la necesidad de empezar a añadir tests para todos los procesos clave de negocio.

Crear tests automáticos para un código fuertemente acoplado significa que vas a acabar haciendo muchos tests de integración y “end to end”. Toda la lógica de negocio estaba contenida en las clases del modelo de datos (el ORM) y cualquier test que hacía uso de esas clases acababa inevitablemente ligado a la base de datos con la consecuencia de tener tests lentos y poco portables (siempre necesitas una base de datos para ejecutarlos).

Primer paso, tests de integración.

El primer paso que tomamos fue empezar a escribir tests para cualquier parte de la web donde un problema pudiera hacernos perder dinero. El código tal y como estaba no se prestaba mucho a ser probado unitariamente, habría requerido de mucho refactoring, pero estando en la situación que estábamos, sin tests, y con poca confianza en el código, eso suponía muchos riesgos. Así nació la primera suite de tests de habitissimo, necesitaba una base de datos y un sistema de fixtures bastante complejo.

Pasar todos los tests era algo muy lento por lo que al final nadie los ejecutaba, necesitábamos poder disponer de un sistema que nos permitiera subir código al repositorio y que los tests se ejecutaran automáticamente, para esto usamos los pipelines de Gitlab.

Para proveer a los tests de una base de datos sobre la que trabajar usamos MyAAS (del que ya hemos hablado antes en este blog). MyAAS carga cada noche un backup fresco de las bases de datos de producción y nos permite levantar varias copias independientes de MySQL, cada ejecución de la suite de tests obtiene su propia base de datos, y cada test se ejecuta dentro de una transacción para no dejar efectos secundarios tras su ejecución. Esto además tiene la ventaja de que nos permite validar cada día que los backups funcionan.

Segundo paso, tests de navegador

Siendo una plataforma multi-país (9 países y creciendo) tenemos muchas cosas en las plantillas que cambian de país a país. A veces un problema tan tonto como mostrar el campo de código postal en un formulario puede tirarte la conversión de un formulario al 0% en países como Chile donde no se utilizan códigos postales.

La experiencia con los tests anteriores ya había sido positiva, en muchas ocasiones los tests nos habían salvado de subir un bug a producción, y queríamos tener la misma seguridad en el front. Unos meses más tarde ya teníamos una suite de tests implementada con selenium. Este conjunto de tests es también bastante lento por lo que inicialmente sólo la ejecutaba el equipo de operaciones antes de un deploy.

Tercer paso, entornos de staging bajo demanada

Los entornos de staging bajo demanda no se crearon con la idea de los tests en la cabeza, pero una vez desarrollado nos dimos cuenta que podíamos explotarlos para lanzar los tests de selenium sobre ellos de forma automatizada cada vez que alguien sube código al repositorio.

Antes de tener entornos bajo demanda teníamos un servidor de test donde teníamos varias instancias de la aplicación (una instancia por cada país), a estas instancias se podían desplegar nuevas versiones de la aplicación con la finalidad de recibir feedback sobre los cambios introducidos, también se podían lanzar los tests de selenium sobre ellos. Sin embargo un número de entornos fijo era un problema, a menudo necesitábamos tener más entornos activos, cada desarrollador al pedir feedback sobre una rama tenía que coger turno para poder desplegar en un entorno de test, los entornos de test se estaban convirtiendo en un cuello de botella.

Para ejecutar los entornos bajo demanda usamos un cluster de Rancher en el que lanzamos una versión miniatura de nuestro entorno de producción con Docker, en estos entornos se ejecutan los mismos componentes que en producción, incluso simulamos el contacto con el mundo exterior con fakes3 para emular un bucket de S3 y Mailhog para emular un servidor de correo SMTP.

Esta aplicación permite a los desarrolladores y a los Product Owners crear un entorno derivado de cualquier rama de git en pocos minutos y poder probarlos, cada entorno tiene su propia base de datos replicada de producción mediante MyAAS.

entornos de staging bajo demanda
Este es el aspecto del panel de control para los entornos de staging

Juntando todo con Gitlab

Finalmente, para unir todo lo que habéis visto hasta ahora, usamos los pipelines de Gitlab. Cada vez que se sube código a cualquier rama del repositorio se lanza un pipeline como el que podéis ver a continuación.

pipeline en gitlab
Nuestro pipeline (haz click sobre la imagen para ampliarla).

El pipeline comienza por crear una base de datos, para eso usa el myaas-proxy, un cliente de MyAAS que se conecta al servidor, solicita una base de datos nueva y luego actúa como proxy hacia ella, este contenedor emula el contrato de las imágenes oficiales de docker para MySQL y Mariadb por lo que puede usarse como reemplazo de estas.

Luego se pasan una serie de tests que en su mayoría no requieren de base de datos, después se despliega un entorno de staging sobre el que se pasarán los tests de selenium. Junto con los tests de selenium lanzamos el grueso de los tests, los de integración, estos los hemos agrupado junto con los de staging simplemente para que no bloqueen los tests de selenium y que se ejecute en paralelo junto a ellos.

Finalmente se pasan los linters y los tests de la guía de estilos y se comprueba si tenemos dependencias con actualizaciones disponibles. Si el código no respeta la guía de estilos el propio test corrige el código y crea un merge request contra la rama original en la que se hacen todos los cambios para ajustarse a las reglas que hemos definido, para ello usamos PHP-CS-Fixer junto a un script propio que se encarga de crear el merge request. Un detalle importante de este script es que solo valida los estilos para los archivos modificados dentro del merge request, no para todo el proyecto, esto nos ha permitido adoptar una guía de estilos e ir aplicándola poco a poco sobre el proyecto, ya que permitir a una herramienta automatizada modificar todo el código del proyecto de golpe da bastante miedo y sería muy complicado revisar todos los cambios.

Invirtiendo la pirámide

Llegados a este punto nuestra principal prioridad es seguir trabajando en bajar el tiempo que tardan todos los tests en pasar, actualmente dependiendo de la carga del cluster los tests pueden tardar entre 20 y 40 minutos en completarse. Para ello estamos trabajando en mover toda la lógica de negocio de los modelos a una capa de servicios en la que aplicamos SOLID, eso nos permite hacer tests unitarios que no dependen de la base de datos ni otros componentes. Si seguimos así antes o después acabaremos dándole la vuelta a nuestra pirámide para tener una como la que Martin Fowler muesta en su artículo TestPyramid, donde los tests unitarios son el grueso del total, son más rápidos y son más baratos de desarrollar.

test pyramid
fuente: Martin Fowler

Conclusión

Nunca es tarde para empezar a hacer las cosas bien, trabajar en un enorme proyecto donde nunca se ha escrito un test no debe desanimarnos a empezar a hacerlo, desde luego habría sido más fácil haber empezado haciendo TDD desde un principio, incluso hoy en día no se puede decir que apliquemos TDD el 100% de la veces, muchas veces primero hacemos el código y luego el test pero es una tendencia que estamos cambiando.

Escribiendo esto y mirando atrás me doy cuenta de todo lo que hemos cambiado desde que un día nos planteamos empezar a escribir tests, superar algunos retos ha sido difícil pero también ha sido muy divertido y hemos creado productos increíbles en este periodo.

Podéis preguntar lo que queráis en la sección de comentarios, estaré encantado de responder a vuestras dudas.

 

Loading Likes...

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *