La suite de pruebas del monolito de Python de Ramp se acercaba poco a poco a los 20 minutos a medida que el equipo superaba las 5000 pruebas, en su mayoría de estilo funcional/de integración. En lugar de reescribir la suite, el equipo de infraestructura atacó directamente el tiempo de ejecución: aumentar la concurrencia de CI corrigiendo un cuello de botella en la configuración de la base de datos que limitaba los trabajadores paralelos a cuatro, dejar de desmontar y reconstruir la base de datos de pruebas desde cero entre ejecuciones locales, y construir un ejecutor de pruebas incremental que solo ejecuta las pruebas relevantes para un cambio de código dado.
La primera versión del ejecutor incremental usaba coverage.py para mapear, para cada prueba, qué líneas de código ejecutaba, y luego, en un nuevo commit, comparaba los archivos modificados con ese mapa. Funcionaba, pero tenía una brecha específica: el código base de Ramp usa clases de modelo de Flask-SQLAlchemy, y la medición de cobertura atribuye casi toda la ejecución a las partes internas de SQLAlchemy en lugar de a la declaración de la clase de modelo en sí. Añadir una columna a un modelo no se registraba como «esta clase cambió» bajo cobertura de líneas puras, por lo que el ejecutor incremental podía omitir pruebas que deberían haber detectado un error que cambiara el esquema.
Para solucionarlo, el equipo reemplazó coverage.py por el sys.settrace integrado de Python, inspeccionando los eventos `call` en busca de un argumento `self` o `cls`, resolviendo ese objeto a su clase y haciendo un recorrido en anchura por `__bases__` para mapear cada clase ancestra al archivo donde se definió. Eso permitió que la instantánea registrara no solo qué líneas ejecutaba una prueba, sino de qué clases de toda su cadena de herencia dependía, de modo que un cambio en la declaración de una clase base invalidaba correctamente las pruebas de todas las subclases, incluso cuando no se ejercitaba directamente ninguna ruta de código de la subclase.
Combinado con la corrección de concurrencia y la omisión de reconstrucciones completas de la base de datos, el ejecutor incremental cubría entre el 30 y el 40 % de la suite en un commit típico y detectaba aproximadamente el 90 % de los fallos que detectaría una ejecución completa, dando a los ingenieros una señal accionable en unos pocos minutos en lugar de los 10-20 completos.
Rastrear la herencia de clases en tiempo de ejecución para que una edición de una clase base no ejercitada siga activando sus pruebas
Coverage.py registra las líneas ejecutadas, no las clases a las que pertenecen, por lo que la ejecución dentro de las partes internas del ORM de SQLAlchemy no se atribuye a la clase de modelo del usuario.
sys.settrace expone los marcos de llamada directamente, por lo que inspeccionar `self`/`cls` recupera «qué clase es esta» independientemente de dónde ocurrió la ejecución a nivel de línea.
Recorrer `__bases__` desde esa clase convierte un evento de llamada en una lista completa de ancestros, por lo que un cambio en una clase base se atribuye a cada archivo de la cadena.
Ejecutar el ejecutor incremental junto con la suite completa, no en lugar de ella, hizo que la brecha de detección de ~10 % nunca se convirtiera en un riesgo de producción.
Un atajo de dependencias basado en cobertura puede pasar por alto dependencias reales, como la herencia, que las herramientas de cobertura no atribuyen al código modificado; conviene comprobar ese punto ciego antes de confiar en la vía rápida.
Ramp consideró que el ejecutor incremental no merecía más inversión a partir de este punto: cerrar la brecha de detección restante de ~10 % requeriría reestructurar en torno a algo como Bazel, que no era la máxima prioridad entonces. La corrección de concurrencia (duplicar el tamaño de la máquina tras eliminar un reinicio redundante de la base de datos por trabajador) por sí sola redujo a la mitad el tiempo de CI, y omitir las reconstrucciones completas de la base de datos en local ahorró de 10 a 30 segundos por ejecución. El equipo publicó el recorrido de herencia basado en sys.settrace como alternativa a opciones de código abierto como pytest-testmon, que carecía del soporte de paralelización de Ramp.
CONSULTA LAS PRUEBAS
Las fuentes
- Faster Python Tests Ramp Engineering (Builders Blog) · builders.ramp.com ·