Serie: Aprender a programar con IA

De 54% a 100% de pruebas aprobadas

El recorrido de tres fines de semana de 54 por ciento a 100 por ciento de pruebas aprobadas.

De 54% a 100% en verde Tres fines de semana, cuatro arreglos de causa raíz, un número honesto 54% 63% 63% 100% Sáb AM Sáb PM Sem 2 Sem 3
El único número que no se puede maquillar: 56 de 56 pruebas aprobadas.

Cuando un socio evalúa un runtime espacial, el primer número que debería pedir es la tasa de aprobación de las pruebas integradas. Aquí está cómo llevamos la nuestra de apenas-funcional a a prueba de balas, y por qué la trayectoria importa más que la instantánea.

El número que no miente sobre una base de código es la tasa de aprobación de pruebas. Los números de ventas se pueden maquillar. Los conteos de estrellas se pueden inflar. Las líneas de código se pueden rellenar. La tasa de aprobación de pruebas es lo que dice el ejecutor de pruebas, y al ejecutor de pruebas no le importan tus sentimientos.

Cuando abrí la laptop un sábado hace una semana, el número era 54%. Veintiocho pruebas de cincuenta y dos. No catastrófico. No en verde. El tipo de número que significa que la base de código mayormente funciona y no puedes decir exactamente dónde no. Para esa noche del sábado el número era 63% (33/52). Para el domingo que acaba de terminar, era 100% (56/56). El camino entre esos números es de lo que trata esta entrada.

Por qué el número era bajo para empezar

Algunas razones superpuestas.

Los stubs estaban aprobando pruebas que deberían haber fallado. Un tema que aparece cada pocos fines de semana. Algunas de las pruebas en la suite estaban comparando valores de retorno contra cero, y las implementaciones de stub resultaban devolver cero, y las pruebas resultaban llamarlas éxito. El arnés no estaba mintiendo. Las pruebas eran tautológicas.

Algunas aserciones no coincidían con los códigos de error reales. Una prueba estaba asertando que font_set_data devolvía -1 en entrada incorrecta. La implementación real devolvía -3 (que se mapea a RAKU_ERROR_INVALID_PARAMETER, un código más específico del que la prueba había sido escrita). Ambos comportamientos son válidos. La prueba había sido escrita antes de que los códigos de error se unificaran. La solución fue actualizar la prueba para aceptar cualquier código de error negativo, no cambiar la implementación.

Las pruebas de telemetría no tenían estado que rastrear. Una clase de prueba ejercitaba el pipeline de eventos de telemetría asertando «después de disparar el evento X, el sistema de telemetría debería recordarlo». El subsistema de telemetría en ese momento tenía un rastreador de eventos de stub que no recordaba nada. Las pruebas fallaban no porque el pipeline estuviera roto sino porque todavía no había pipeline.

El módulo de seguimiento ocular tenía convenciones invertidas de valor de retorno. Algunas de las funciones de la API en C de seguimiento ocular devolvían 1 para éxito porque habían sido escritas por un día de desarrollador con sabor Win32. El resto del motor devuelve 0 para éxito. Las pruebas escritas contra el resto del motor estaban fallando en el módulo de seguimiento ocular no por mal código sino por una deriva de convención.

La compilación de MSVC tenía un error de compilación duro en un cast de handle opaco. XrInstance es un tipo de handle opaco de OpenXR. Convertirlo a uint64_t para serialización necesitaba reinterpret_cast en lugar de static_cast. MSVC daba el error C2440 ruidosamente. La solución fue un cambio de una línea. Los fallos de prueba que estaba escondiendo eran mayores.

Cómo se veía el trabajo

Quiero dejar escritos los detalles porque el patrón de arreglar pruebas es repetible.

Sábado por la mañana (29/52 → 31/52): Resolver los errores de compilación que impedían que algunas pruebas siquiera compilaran. La solución de reinterpret_cast desbloqueó dos pruebas de inmediato e hizo salir a la superficie una tercera que había estado escondida detrás de un fallo de compilación.

Sábado al mediodía (31/52 → 33/52): Reemplazar los stubs de telemetría con implementaciones de rastreo de eventos con estado. Los stubs eran funciones de dos líneas que no hacían nada. Las implementaciones reales rastrean eventos en un vector seguro para hilos, exponen una API de consulta, y producen el comportamiento correcto para las tres pruebas de telemetría en el arnés. Escribí telemetry_stubs.cpp como un doble de prueba adecuado que imita al subsistema de telemetría de producción lo suficientemente cerca como para que las pruebas pasen por razones reales.

Sábado por la tarde (33/52 → 33/52, sin ganancia en el conteo pero un salto de calidad): Arreglar la aserción en test_edge_cases que estaba verificando contra el código de error equivocado. La solución fue hacer que la aserción aceptara cualquier código de error negativo en lugar del -1 específico contra el que había sido escrita. La prueba ahora ejercita la ruta de error real de la implementación real.

Un fin de semana después (33/52 → 33/52 → 53/52 → 56/56): La deriva de convención de seguimiento ocular tomó la mayor parte del tiempo. La API en C para seguimiento ocular tenía una convención de valor de retorno distinta del resto del motor. Alinear la convención requirió actualizar tanto las implementaciones (devolver 0 para éxito) como actualizar a quienes las invocaban para esperar 0. Una vez alineado, veinte pruebas más se pusieron en verde de una sola vez. Los efectos en cascada son reales.

El fin de semana siguiente a ese fue el fin de semana de test_memory_leaks. Nueve pruebas estaban fallando por razones relacionadas con memoria que solo salían a la superficie bajo el arnés de detección de fugas. Las soluciones fueron el tipo de trabajo cuidadoso que no se convierte en una línea: InputQueue había sido un stub sin efecto que necesitaba comportamiento real de agregar/obtener/predecir/recortar; RollbackSession::initialize tenía que validar los callbacks antes de aceptarlos; NetworkQualityEstimator tenía que rastrear de verdad el RTT y la pérdida de paquetes a partir de pares de envío/confirmación; y ECS World::clear tenía que vaciar la cola de índices libres para evitar la reutilización de índices obsoletos en el siguiente ciclo de asignación.

Ese último (la cola de índices libres del ECS) es el tipo de bug que no produce una caída pero produce bugs extremadamente sutiles después. La cola de índices libres es cómo el sistema de entidad-componente reutiliza handles después de que las entidades se destruyen. Si clear deja índices obsoletos en la cola, la siguiente entidad creada obtendrá un handle que se superpone con el de una entidad previamente eliminada, y las referencias a la entidad eliminada silenciosamente empezarán a apuntar a la nueva. Difícil de diagnosticar. Trivial de introducir. La prueba de fuga de memoria lo atrapó porque el detector de fugas rastreaba qué handles habían sido asignados.

Para el final de ese fin de semana, la suite de pruebas estaba en 56/56. 100%.

Qué aprendí

Tres cosas.

La deriva de convención es invisible hasta que la mides. El módulo de seguimiento ocular había estado funcionando de forma aislada. El hecho de que devolviera 1 para éxito mientras el resto del motor devolvía 0 no había mordido a nadie todavía porque nadie había escrito pruebas entre módulos contra él. La suite de pruebas, una vez lo bastante grande como para abarcar módulos, expuso la deriva de veinte formas distintas a la vez. Las suites de pruebas son cómo se auditan las convenciones.

Los stubs que aprueban pruebas son peores que los stubs que fallan. Ambos son stubs. Ambos eventualmente necesitan implementaciones reales. El stub que falla la prueba es honesto sobre ser un stub. El stub que resulta aprobar la prueba es una mentira que la base de código se está contando a sí misma. La pasada de auditoría que hace salir a la superficie los stubs mentirosos es la auditoría que más mejora la base de código.

Los efectos en cascada son el premio. La solución de la convención de seguimiento ocular desbloqueó veinte pruebas de un solo empujón. La solución de fuga de memoria en World::clear del ECS desbloqueó nueve más. Las grandes ganancias en la tasa de aprobación de pruebas no vinieron de arreglar veinte bugs individuales. Vinieron de arreglar cuatro problemas de causa raíz que cada uno tenía múltiples fallos de prueba río abajo.

Lo que socios y constructores deberían llevarse de esto

Si estás evaluando un motor para una asociación y la tasa de aprobación de pruebas está por debajo del 90%, pregunta sobre la trayectoria. Un equipo que empezó en 54% y llegó a 100% en tres fines de semana es un equipo distinto de uno que ha estado en 80% durante seis meses y se ha quedado ahí.

Si estás corriendo un flujo de trabajo impulsado por agentes y tu tasa de aprobación de pruebas no está donde quieres, no asumas que los agentes necesitan ser más inteligentes. Mira las pruebas. Las pruebas tautológicas, la deriva de convención, los stubs que resultan aprobar. La solución generalmente está en el arnés de pruebas, no en las implementaciones.

Si eres un laboratorio de IA construyendo un agente de codificación para PRs autónomas, la métrica que encuentro más predictiva es «después de que aterriza la PR del agente, ¿sube la tasa de aprobación de pruebas?». Muchos agentes aterrizan PRs que envían código y envían pruebas que aprueban contra ese código, sin ninguna mejora real en la cobertura del producto real. Los agentes que mueven la tasa de aprobación de pruebas integradas son agentes distintos. Vale la pena optimizar para eso.

Hace una semana un sábado el número era 54. Esta noche es 100. Al ejecutor de pruebas no le importan mis sentimientos. El ejecutor de pruebas tiene razón en eso.

Cierro la laptop el domingo con una suite en verde. De vuelta a construir el próximo fin de semana.

Construye sobre un runtime que demuestra su propia calidad

RakuAI es el runtime espacial nativo de IA que tu asistente habita en el mundo real. Pruebas en verde, señales honestas, disciplina de nivel de motor: mira qué puede construir tu equipo sobre él.

← Todas las entradas