DEV Community

Cover image for Cachear la suite de tests sin certificar un verde falso
Guillermo Leyendeker
Guillermo Leyendeker

Posted on Originally published at leyendeker.com

Cachear la suite de tests sin certificar un verde falso

La primera tarea del plan reescrito era la más aburrida posible: el control de calidad corría la suite completa de tests aunque el código no hubiera cambiado. Cachear ese resultado es una optimización de manual.

La hice el mismo día. Y en el camino entendí por qué esta optimización, que parece trivial, es de las que más fácil se implementan mal.

El punto de partida

El control corría sus verificaciones en serie, una atrás de la otra. La suite completa, con un timeout de 40 minutos. Después los builds de cada frontend, con 5 minutos cada uno. Y al final el análisis de mutaciones, con 20.

Ese timeout de 40 minutos dice bastante: en mayo lo había subido de 10 a 40, porque la suite ya no entraba. La curva iba en una sola dirección.

Pero lo peor no era la duración. Era esto: cuando una tarea fallaba el control y había que reintentarla tocando solamente el frontend, al volver a verificar se corría de nuevo la suite entera del backend, que no se había modificado en absoluto. Minutos de cómputo para confirmar algo que ya sabíamos.

Dos cambios, uno obvio y uno delicado

El obvio: correr la suite y los builds de frontend en paralelo en vez de en serie. No hay ninguna razón para que esperen uno al otro, más allá de que así se había escrito.

El delicado: cachear el resultado verde de la suite por la combinación de proyecto, repositorio y hash del commit. Si ya corrimos los tests sobre exactamente este código y pasaron, no hace falta correrlos de nuevo.

Escrito así suena inofensivo. Pero un caché de resultados de tests es, en el fondo, una promesa: estás afirmando que el estado que se midió es el mismo que se va a desplegar. Si esa promesa se rompe, el sistema no falla — hace algo peor, que es certificar como aprobado un código que nunca se probó.

Las dos condiciones que sostienen la promesa

Por eso el caché tiene dos candados, y ninguno es opcional.

El árbol de trabajo tiene que estar limpio. Si hay cambios sin commitear, el hash del commit ya no describe lo que hay en el disco. El código que se va a evaluar no es el que se testeó. En ese caso el caché ni se consulta: se corre la suite.

El resultado vale 24 horas. No porque el código cambie solo, sino porque el entorno sí: dependencias, contenedores, base de datos de pruebas, servicios externos. Un verde de hace una semana dice poco sobre el estado de hoy.

Hay un tercer detalle, más chico pero que me gusta: el candado de exclusión de la suite —el que evita que dos corridas se pisen— solo se toma si efectivamente se va a correr algo. Cuando el caché acierta, la función devuelve antes de llegar a esa línea. Sin eso, estaríamos serializando corridas que ni siquiera van a ocurrir.

La marca que permite auditar

Esta es la parte que considero más importante de toda la implementación, y la que menos se ve escrita en otros lados.

Cada resultado que viene del caché viaja marcado como tal dentro de los datos del control de calidad. Queda registrado que esa tarea no corrió los tests: se apoyó en una corrida anterior.

¿Para qué sirve eso? Para el día en que aparezca un verde que no debía serlo. Ese día voy a poder reconstruir exactamente qué tareas se apoyaron en caché y cuáles corrieron de verdad, y ver si el problema estuvo ahí.

Sin esa marca, un falso verde es indistinguible de un verde legítimo. Tenés un bug que no podés ni acotar, porque no sabés sobre qué universo de tareas buscar.

Es un principio que aplico a cualquier optimización que reemplace una verificación real por una inferencia: toda promesa automatizada necesita dejar la marca que permita auditarla cuando falle. Porque va a fallar alguna vez, y ese día la marca es lo único que tenés.

Lo que quedó deliberadamente afuera

El análisis de mutaciones —que verifica si los tests realmente detectan cambios en el código, no solamente si pasan— quedó fuera del caché a propósito.

La razón es que mide algo distinto. La suite se evalúa contra el estado del repositorio; las mutaciones se evalúan contra el diff de la tarea puntual. Dos tareas sobre el mismo commit base pueden tener diffs completamente distintos, así que cachear por hash de commit no tendría sentido: son cosas ortogonales.

Lo que sí le agregué fue un cortocircuito por el otro lado: si el diff de la tarea no contiene código mutable, el análisis directamente no corre. No hay nada que mutar. Eso resolvió una porción grande de los casos sin tocar la lógica de fondo.

El resultado

Entre el paralelismo y el caché, el control de calidad dejó de ser el cuello de botella evidente que era. Sigue siendo el paso más caro del sistema —ese 29% de la factura no se movió tanto como esperaba— pero la espera por tarea bajó de forma notoria.

Y quedó, sobre todo, un criterio que después usé varias veces: acelerar un control está bien mientras puedas demostrar después que el control existió. Si la aceleración te deja sin forma de auditar qué se verificó y qué no, no estás optimizando: estás aflojando el control y llamándolo de otra manera.

El frente que quedó abierto

Esa misma revisión de junio dejó otro tema sobre la mesa, que no era de velocidad y era bastante más incómodo.

Había un componente que venía usando hacía más de un mes, que se ejecutaba más de mil veces al mes, y del cual no podía decir si mejoraba algo. No fallaba nunca. Ese era exactamente el problema.

De eso trata la próxima entrega.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.