Diário da fábrica

Capítulo 04 ·

O gargalo não é o código, é o teste

Somando o tempo das etapas, testar levou mais do que desenvolver. A próxima grande economia está no teste.

⏱️ 10 min de leitura

No capítulo anterior, a fábrica passou a primeira noite inteira trabalhando sem mim. Acordei com 10 tarefas entregues, testadas e integradas. Mas, quando somei o tempo das etapas daquela noite, uma conta não fechava. Testar levou mais do que desenvolver: cerca de 3h15 contra 2h10. E 1h20 desse teste foi um agente esperando um comando terminar.

A pista já estava no capítulo 1, Escala 6x1? Aqui é 24x7: a segunda execução real levou quase 40 minutos, com a integração contínua rodando quatro vezes até passar. Na hora, eu achei que era coisa de começo. Era o padrão.

Este capítulo é sobre essa conta: por que escrever código deixou de ser a parte cara do trabalho, e o que eu mudei para as noites seguintes não gastarem o relógio esperando.

A primeira noite, tempo somado das etapas: desenvolver levou 2h10; testar levou 3h15, das quais 1h20 foram um agente esperando um comando terminar

Onde o tempo ia

Quando olhei para dentro das execuções, a surpresa não foi o teste demorar. Foi onde o tempo ia.

Antes, uma legenda para o resto do capítulo:

Rodar um teste é barato. Um comando roda a suíte, ela passa ou falha, e acabou. O caro era tudo o que acontecia em volta, quando quem testa é uma IA:

Não é só o contexto, é o turno

Eu jurava que a segunda noite tinha ficado mais lenta porque cinco execuções disputavam o processador. Quando a IA cruzou os números, deu outra coisa: a suíte levava até um pouco menos que na primeira noite. O que tinha crescido era o número de turnos do agente de testes, que passou de 400 em uma só execução.

Quando se fala em economizar com IA, quase todo mundo pensa no contexto: o quanto de texto entra na conversa. Só que o modelo relê o contexto inteiro a cada turno. Toda vez que o agente roda um comando, lê um arquivo ou confere um resultado, é mais uma leitura de tudo. Uma conta simples mostra o tamanho disso:

Duas sessões: uma com 500 mil tokens de contexto e 5 turnos lê 2,5 milhões de tokens; outra com 250 mil e 20 turnos lê 5 milhões. Na fábrica, o agente de testes de uma única execução fez mais de 200 chamadas de ferramenta, com contexto médio perto de 240 mil tokens, e releu cerca de 37 milhões de tokens

Na fábrica, os números eram bem maiores. Em uma execução de 2 de outubro, o agente de testes fez mais de 200 chamadas de ferramenta, com um contexto médio perto de 240 mil tokens. No fim, ele tinha relido cerca de 37 milhões de tokens, e essa releitura respondeu por cerca de três quartos do que ele custou.

Os provedores guardam por alguns minutos o que acabou de ser lido, e reler esse trecho custa uma fração de um token novo. Mas um agente que fica esperando um comando longo pode voltar com essa memória expirada e pagar a leitura inteira de novo.

Cuidar do que entra no contexto ajuda. Cuidar de quantas vezes ele é relido ajuda tanto quanto.

A pirâmide de cabeça para baixo

Quem trabalha com software conhece a pirâmide de testes. Na base, muitos testes unitários, pequenos, rápidos e baratos. No topo, poucos testes de ponta a ponta, que abrem o aplicativo e simulam um usuário, lentos e caros.

Três pirâmides coloridas por quem faz o trabalho: verde para o motor, sem IA, e laranja para o agente. Na clássica, tudo roda sem IA e a base de testes unitários é rápida e barata. Com agentes e sem cuidado, tudo passa pelo agente, a pirâmide se inverte e o unitário fica com 64% do custo. Na que funcionou, a base é a execução pelo motor, em verde, e o meio e o topo são do agente: os testes escritos uma vez, antes do código, e o julgamento

A pirâmide sempre foi ordenada pelo custo de executar o teste. Com agentes, o que manda é outro custo: o de escrever o teste, esperar por ele e interpretar o resultado, pago em tokens. E esse custo pesa mais justamente na base, que é onde ficam mais testes.

Os números confirmaram. O teste respondia por quase metade do custo de uma execução. Quando eu acrescentei os testes de comportamento, que descrevem o que o usuário faz na tela, passou de 60%. Em uma execução, o agente de testes sozinho ficou com cerca de dois terços do custo.

Também tentei o outro extremo e desliguei os testes em uma execução. Ela saiu barata e rápida, e deixou passar três regressões, coisas que funcionavam e pararam de funcionar.

O motor executa, o agente escreve uma vez

A resposta foi separar dois trabalhos que estavam misturados e gastar IA só onde existe uma decisão a tomar. O agente escreve o teste uma vez, e o motor roda sempre.

Tudo o que era execução saiu da mão do agente:

O agente ficou com o que pede julgamento:

A terceira noite: o custo despencou, o relógio não

A primeira noite com a ferramenta atualizada mostrou os dois lados da mudança.

O custo do teste por execução caiu para uma fração do que era, algo entre um quinto e um vigésimo. Em tokens, o problema estava resolvido.

No relógio, não. A suíte, agora rodada pelo motor, passou a levar cerca de 30 minutos por rodada, contra 4 a 11 antes. Foram quatro rodadas naquela noite, e três estouraram o tempo limite. Uma delas levou a execução até um ponto do fluxo em que só eu decido, e ela ficou parada esperando eu acordar. A automação não atravessou a minha aprovação, como deve ser. Mas aquela execução parou até de manhã.

Levar o teste pesado para o fechamento de uma versão era, na prática, reconstruir a integração contínua de outro jeito. Na época, escrevi:

"Comecei com o gargalo no CI e voltei com o gargalo no CI. É engraçado como o gargalo faz um loop."

Testes antes do código

Faltava uma pergunta: quem escreve o teste?

Desde o começo, a regra era que o agente que desenvolve nunca escreve os próprios testes, para não testar o próprio viés. Só que o agente de testes vinha depois do desenvolvimento e lia o código pronto. Pagava a conta de reler tudo e, mesmo assim, escrevia os testes a partir do que o código fazia, e não do que a tarefa pedia.

Então inverti a ordem em um fluxo novo de desenvolvimento. Com o exemplo do aplicativo de tarefas (To Do) do capítulo 2, o pedido "quero adicionar uma data prevista nas tarefas" passa a andar assim:

A quarta noite

Na noite seguinte, o fluxo novo rodou de verdade:

Uma coisa não mudou: o revisor encontrou problemas graves em 7 das 10 execuções. Os testes não substituíram a revisão, e a revisão não substituiu os testes.

Teve desperdício, claro. Duas vezes o desenvolvedor esqueceu de colocar nos elementos da tela o data-testid, a marcação que os cenários usam para achar cada botão, campo ou card. Sem ela, o teste não encontra o elemento e falha. Uma rodada inteira da suíte foi jogada fora em cada uma. A correção é barata, e é trabalho do motor ⚙️: conferir se cada data-testid que os cenários pedem existe na tela antes de rodar a suíte.

O gargalo andou de novo

Não era a primeira vez que o gargalo mudava de lugar. Ele começou na integração contínua, que rodava em um serviço externo. Depois foi para os conflitos na hora de juntar o código, para a forma como o código estava organizado, para o planejamento, para o próprio computador, que travou com quatro execuções pesadas ao mesmo tempo, e voltou para o teste.

Um ciclo com seis etapas por onde o gargalo passou: integração contínua em um serviço externo, conflitos ao juntar o código, organização do código, planejamento das tarefas, o computador que travou com quatro execuções e o teste. A última seta, do teste de volta para a integração contínua, está em destaque: o teste pesado na versão é a integração contínua de novo

Resolvido o teste, ele andou mais uma vez. Comparado com o fluxo anterior, o novo cortou um terço dos tokens por tarefa. O agente de testes, que ficava com quase metade dos tokens, passou a ficar com pouco mais de um décimo. Agora era o desenvolvedor que ficava com três quartos, e mais da metade das execuções voltava para ele pelo menos uma vez para corrigir alguma coisa.

A próxima economia está nessas voltas: testes de aceite mais claros e checagens baratas antes da suíte valem mais do que trocar o modelo de IA.

O próximo passo

Aquela 1h20 de um agente esperando um comando terminar não era só um problema de teste. A execução mais cara do projeto inteiro não entregou nada: um agente passou o tempo perguntando, de novo e de novo, se algo tinha terminado.

No próximo capítulo, capítulo 5, O token mais caro é o de uma IA esperando, conto essa execução e a regra que nasceu dela: esperar é trabalho de código.

Próximo capítuloO token mais caro é o de uma IA esperandoA execução mais cara não entregou nada: uma IA consultando um status que não mudava. Esperar é trabalho de código. ← Todos os capítulos

Newsletter

Receba os próximos capítulos.

Um e-mail quando sair um capítulo novo. Sem spam, e você sai quando quiser.