De 54% a 100% de Testes Passando
Quando um parceiro avalia um runtime espacial, o primeiro número que ele deveria pedir é a taxa de aprovação dos testes integrados. Aqui está como levamos a nossa de mal-funcionando para à prova de balas — e por que a trajetória importa mais do que a foto do momento.
O número que não mente sobre uma base de código é a taxa de aprovação dos testes. Números de vendas podem ser maquiados. Contagens de estrelas podem ser infladas. Linhas de código podem ser infladas. A taxa de aprovação dos testes é o que o executor de testes diz que é, e o executor de testes não se importa com seus sentimentos.
Quando abri o laptop no sábado de uma semana atrás, o número era 54%. Vinte e oito testes de cinquenta e dois. Não catastrófico. Não verde. O tipo de número que significa que a base de código funciona na maior parte e você não consegue dizer exatamente onde não funciona. Naquele sábado à noite, o número era 63% (33/52). No domingo que acabou de terminar, era 100% (56/56). O caminho entre esses números é sobre o que este post é.
Por que o número era baixo para começar
Algumas razões que se sobrepõem.
Stubs estavam passando testes que deveriam ter falhado. Um tema que aparece a cada poucos fins de semana. Alguns dos testes na suíte estavam comparando valores de retorno com zero, e as implementações de stub por acaso retornavam zero, e os testes por acaso chamavam isso de sucesso. O harness não estava mentindo. Os testes eram tautológicos.
Algumas asserções estavam desalinhadas com os códigos de erro reais. Um teste estava afirmando que font_set_data retornava -1 em entrada inválida. A implementação real retornava -3 (que mapeia para RAKU_ERROR_INVALID_PARAMETER, um código mais específico do que o teste foi escrito para esperar). Ambos os comportamentos são válidos. O teste tinha sido escrito antes dos códigos de erro serem unificados. A correção foi atualizar o teste para aceitar qualquer código de erro negativo, não mudar a implementação.
Testes de telemetria não tinham estado para rastrear. Uma classe de teste exercitava o pipeline de eventos de telemetria afirmando “depois que eu disparo o evento X, o sistema de telemetria deveria se lembrar dele.” O subsistema de telemetria na época tinha um rastreador de eventos stub que não se lembrava de nada. Os testes falhavam não porque o pipeline estava quebrado, mas porque ainda não havia pipeline nenhum.
O módulo de rastreamento ocular tinha convenções de valor de retorno invertidas. Algumas das funções da API em C de rastreamento ocular estavam retornando 1 para sucesso porque tinham sido escritas em um dia de desenvolvimento com sabor Win32. O resto do engine retorna 0 para sucesso. Testes escritos contra o resto do engine estavam falhando no módulo de rastreamento ocular não por causa de código ruim, mas por um desvio de convenção.
O build do MSVC tinha um erro de compilação rígido em um cast de handle opaco. XrInstance é um tipo de handle opaco do OpenXR. Fazer o cast dele para uint64_t para serialização precisava de reinterpret_cast em vez de static_cast. O MSVC dava erro C2440 bem alto. A correção foi uma mudança de uma linha. As falhas de teste que ela estava escondendo eram maiores.
Como o trabalho foi
Quero registrar os detalhes porque o padrão de correção de testes é repetível.
Sábado de manhã (29/52 → 31/52): Resolver os erros de build que estavam impedindo alguns testes de sequer compilar. A correção do reinterpret_cast desbloqueou dois testes imediatamente e revelou um terceiro que tinha estado escondido atrás de uma falha de compilação.
Sábado ao meio-dia (31/52 → 33/52): Substituir os stubs de telemetria por implementações de rastreamento de eventos com estado. Os stubs eram funções de duas linhas que não faziam nada. As implementações reais rastreiam eventos em um vetor thread-safe, expõem uma API de consulta, e produzem o comportamento certo para os três testes de telemetria no harness. Escrevi telemetry_stubs.cpp como um dublê de teste de verdade que imita o subsistema de telemetria de produção o suficiente para que os testes passem por razões reais.
Sábado à tarde (33/52 → 33/52, sem ganho na contagem mas com um salto de qualidade): Corrigir a asserção em test_edge_cases que estava checando contra o código de erro errado. A correção foi fazer a asserção aceitar qualquer código de erro negativo em vez do -1 específico contra o qual tinha sido escrita. O teste agora exercita o caminho de erro real da implementação real.
Um fim de semana depois (33/52 → 33/52 → 53/52 → 56/56): O desvio de convenção do rastreamento ocular levou mais tempo. A API em C para rastreamento ocular tinha uma convenção de valor de retorno diferente do resto do engine. Alinhar a convenção exigiu atualizar tanto as implementações (retornar 0 para sucesso) quanto atualizar quem chamava para esperar 0. Uma vez alinhado, vinte testes a mais ficaram verdes de uma vez. Efeitos em cascata são reais.
O fim de semana seguinte foi o fim de semana do test_memory_leaks. Nove testes estavam falhando por razões relacionadas a memória que só apareciam sob o harness de detecção de vazamentos. As correções foram do tipo de trabalho cuidadoso que não vira uma mudança de uma linha: InputQueue tinha sido um stub que não fazia nada e precisava de comportamento real de adicionar/obter/prever/aparar; RollbackSession::initialize tinha que validar callbacks antes de aceitá-los; NetworkQualityEstimator tinha que de fato rastrear RTT e perda de pacotes a partir de pares de envio/confirmação; e ECS World::clear tinha que esvaziar a fila de índices livres para evitar reuso de índice obsoleto no próximo ciclo de alocação.
Essa última (a fila de índices livres do ECS) é o tipo de bug que não produz um crash, mas produz bugs extremamente sutis mais tarde. A fila de índices livres é como o sistema de entidade-componente reutiliza handles depois que entidades são destruídas. Se clear deixa índices obsoletos na fila, a próxima entidade criada vai receber um handle que se sobrepõe ao de uma entidade previamente deletada, e referências à entidade deletada vão silenciosamente começar a apontar para a nova. Difícil de diagnosticar. Trivial de introduzir. O teste de vazamento de memória o pegou porque o detector de vazamento rastreava quais handles tinham sido alocados.
Ao final daquele fim de semana, a suíte de testes estava em 56/56. 100%.
O que eu aprendi
Três coisas.
Desvio de convenção é invisível até você medir. O módulo de rastreamento ocular tinha estado funcionando isoladamente. O fato de que ele retornava 1 para sucesso enquanto o resto do engine retornava 0 não tinha mordido ninguém ainda porque ninguém tinha escrito testes entre módulos contra ele. A suíte de testes, uma vez grande o suficiente para abranger módulos, expôs o desvio de vinte formas diferentes de uma vez. Suítes de teste são como convenções são auditadas.
Stubs que passam nos testes são piores do que stubs que falham. Ambos são stubs. Ambos eventualmente precisam de implementações reais. O stub que falha no teste é honesto sobre ser um stub. O stub que por acaso passa no teste é uma mentira que a base de código está contando a si mesma. A passagem de auditoria que revela os stubs mentirosos é a auditoria que mais melhora a base de código.
Efeitos em cascata são o prêmio. A correção de convenção do rastreamento ocular desbloqueou vinte testes em um único push. A correção de vazamento de memória em ECS World::clear desbloqueou mais nove. Os grandes ganhos na taxa de aprovação de testes não vieram de corrigir vinte bugs individuais. Vieram de corrigir quatro questões de causa raiz que cada uma tinha múltiplas falhas de teste a jusante.
O que parceiros e construtores devem tirar disso
Se você está avaliando um engine para parceria e a taxa de aprovação de testes está abaixo de 90%, pergunte sobre a trajetória. Um time que começou em 54% e foi para 100% em três fins de semana é um time diferente de um que está em 80% há seis meses e ficou lá.
Se você está rodando um fluxo de trabalho orientado por agentes e sua taxa de aprovação de testes não está onde você quer, não presuma que os agentes precisam ser mais inteligentes. Olhe para os testes. Os testes tautológicos, o desvio de convenção, os stubs que por acaso passam. A correção geralmente está no harness de teste, não nas implementações.
Se você é um laboratório de IA construindo um agente de codificação para PRs autônomos, a métrica que considero mais preditiva é “depois que o PR do agente é integrado, a taxa de aprovação de testes sobe.” Muitos agentes integram PRs que entregam código e entregam testes que passam contra esse código, sem nenhuma melhoria real na cobertura do produto de verdade. Agentes que movem a taxa de aprovação de testes integrados são agentes diferentes. Vale a pena otimizar para isso.
No sábado de uma semana atrás o número era 54. Esta noite é 100. O executor de testes não se importa com meus sentimentos. O executor de testes está certo sobre isso.
Fechando o laptop no domingo com uma suíte verde. De volta a construir no próximo fim de semana.
Construa sobre um runtime que prova sua própria qualidade
O RakuAI é o runtime espacial nativo em IA onde seu assistente habita no mundo real. Testes verdes, sinais honestos, disciplina de nível de engine — veja o que seu time pode entregar com ele.