Engenharia
A IA tornou o código barato, mas a confiança continua cara
A IA tornou gerar código barato. Confiar nas mudanças virou o gargalo — e uma boa suíte de testes é o que sustenta essa confiança.
Com a evolução dos coding agents e dos diversos fluxos de desenvolvimento autônomo que surgiram nos últimos anos, uma coisa ficou evidente: produzir código nunca foi tão barato. O principal gargalo do desenvolvimento de software deixou de ser escrever código e passou a ser confiar nas alterações que estão sendo realizadas.
Hoje é comum trabalhar com agentes que analisam um problema, sugerem diferentes abordagens de implementação e recomendam qual delas faz mais sentido para determinado contexto. Após aprovarmos um plano de execução, seja em um fluxo iterativo ou em um one-shot prompt, o agente é capaz de implementar uma feature completa em poucos minutos. O processo parece simples: código entregue, tokens consumidos e tarefa finalizada.
Esse é o caminho feliz.
Na prática, sabemos que o trabalho não termina quando o código é gerado. Pelo contrário, é justamente nesse momento que surge a principal pergunta: como podemos confiar que as alterações realizadas estão corretas e não introduziram regressões no sistema?
Os agentes tendem a produzir resultados melhores quando trabalham em tarefas menores e bem definidas, porém, isso não significa que pequenas alterações sejam necessariamente pouco arriscadas. Uma simples mudança pode modificar contratos, quebrar integrações ou introduzir efeitos colaterais difíceis de identificar durante uma revisão manual.
A IA resolveu grande parte do problema de geração de código, mas não resolveu problemas fundamentais da engenharia de software, como contexto de negócio, consistência arquitetural, contratos entre componentes e garantias de comportamento do sistema. Esses continuam sendo desafios que precisam ser tratados pelo processo de desenvolvimento.
É justamente nesse ponto que uma boa suíte de testes se torna ainda mais relevante.
Os testes sempre foram importantes
Os testes sempre foram importantes, muito antes da existência dos coding agents. Testes unitários, testes de integração e pipelines de validação sempre tiveram o papel de proteger a evolução do software, garantir segurança durante o processo de deploy e reduzir o risco de regressões. A chegada da IA não mudou esse fato; ela apenas aumentou o retorno que obtemos ao investir em uma suíte de testes bem construída.
Dentro da pirâmide de testes, os testes unitários continuam sendo um dos mecanismos de feedback mais rápidos e baratos que podemos ter. Eles permitem validar comportamentos específicos do sistema em poucos segundos, fornecendo confiança para alterações no código e acelerando o ciclo de desenvolvimento.
Mais do que mecanismos de validação, os testes também funcionam como documentação executável do comportamento esperado do sistema. Eles fornecem contexto para desenvolvedores, pipelines de entrega contínua e, agora, também para agentes responsáveis por implementar e evoluir o software.
No contexto do desenvolvimento assistido por IA, os testes também passaram a ser uma das formas mais precisas de especificar comportamento para os agentes. Um teste falhando costuma ser muito menos ambíguo do que uma descrição textual de um requisito. Quanto mais explícitos forem os contratos do sistema, maior tende a ser a capacidade do agente de propor e validar alterações com segurança.
Uma suíte de testes robusta não protege apenas a aplicação em produção. Ela estabelece guard rails claros sobre o que pode ou não ser alterado, permitindo que agentes operem com maior autonomia e segurança. Quanto maior for a autonomia que delegamos à IA durante o desenvolvimento, maior será a necessidade de mecanismos capazes de garantir que o comportamento esperado do sistema foi preservado.
TDD e design de software
Nunca foi simples adotar testes unitários como parte do fluxo de desenvolvimento. Seja pela dificuldade de começar escrevendo testes utilizando TDD, pela complexidade de manter alta coesão e baixo acoplamento no código ou pelo desafio de pensar de forma suficientemente granular para permitir a evolução do software ao longo do tempo, construir uma boa suíte de testes sempre exigiu disciplina de engenharia.
Mas existe um efeito colateral extremamente positivo nisso tudo: bons testes normalmente são consequência de um bom design de software.
Independentemente de seguir TDD rigorosamente ou não, manter os testes como parte da cultura de desenvolvimento tende a nos levar a uma melhor modelagem do domínio, componentes mais desacoplados e contratos mais explícitos sobre o comportamento esperado do sistema.
Uma vez que esses contratos existem, eles passam a ser consumidos não apenas por desenvolvedores, mas também pelos agentes responsáveis por evoluir o software. Os testes deixam de ser apenas uma ferramenta de validação e passam a fazer parte da própria especificação do sistema.
Desenvolvimento assistido por IA
Em um fluxo moderno de desenvolvimento assistido por IA, é comum que o processo se pareça com algo próximo disso:
flowchart LR A[Requisitos] B[Especificação] C[Especificação + Testes] D[Implementação com IA] E[Review local com IA] F[Validação] G[CI] H[Deploy] I[Acompanhamento com IA] A --> B B --> C C --> D D --> E E --> F F --> G G --> H H --> I
Quando pensamos nesse pipeline, fica claro que os testes exercem um papel muito maior do que simplesmente validar se uma feature está funcionando. Eles funcionam como guard rails para os agentes, limitando o espaço de erro durante a implementação e aumentando significativamente a confiança nas alterações realizadas.
Isso permite delegar tarefas cada vez mais complexas para os agentes. Grandes refactors, alterações arquiteturais, migrações de bibliotecas e evolução de APIs deixam de ser mudanças que geram receio constante e passam a ser alterações protegidas por contratos previamente estabelecidos pela suíte de testes.
A pergunta deixa de ser “o agente consegue fazer?” e passa a ser “como sabemos que ele fez corretamente?”.
Com uma suíte de testes madura, passamos a reduzir drasticamente a necessidade de validações manuais durante o desenvolvimento, uma vez que grande parte das verificações já estão automatizadas dentro da pipeline de entrega.
Vale notar que os agentes são excelentes produtores de código e pull requests, mas continuam dependendo dos mesmos mecanismos que nós utilizamos há anos para gerar confiança no software. O papel da pipeline de validação continua sendo exatamente o mesmo: responder se aquela alteração está pronta para seguir para produção.
Bug fixing orientado por testes
Outro ponto que tem nos ajudado bastante é o fluxo de resolução de bugs utilizando agentes. Em vez de iniciar uma correção diretamente no código, o desenvolvimento parte da criação de um teste que reproduz exatamente o comportamento observado em produção.
O fluxo é relativamente simples:
flowchart LR A[Bug reportado] B[Criação do teste falhando] C[Fix com IA] D[Testes passando] E[Validação no CI] F[Deploy] A --> B B --> C C --> D D --> E E --> F
Essa abordagem possui duas grandes vantagens. A primeira é garantir que o problema foi efetivamente reproduzido antes da implementação da correção. A segunda é transformar o próprio bug em um novo contrato do sistema, evitando que ele seja introduzido novamente no futuro.
Além disso, ela conversa muito bem com a forma como os agentes trabalham. Um teste reproduzindo exatamente o comportamento incorreto fornece um contexto extremamente objetivo sobre o que precisa ser corrigido e qual é o resultado esperado após a implementação.
Testes de integração e consistência do sistema
Por fim, para aplicações que prestam serviços e possuem integrações com parceiros, os testes de integração exercem um papel igualmente importante. Uma cobertura adequada dos fluxos de integração nos garante consistência na camada de APIs e nos contratos estabelecidos entre os componentes do sistema.
Mesmo que um desenvolvedor ou um agente realize alterações em diferentes camadas da aplicação, a suíte de testes continua sendo responsável por garantir a integridade do serviço e a preservação dos comportamentos esperados.
Podemos enxergar isso da seguinte forma:
flowchart LR A[Alteração no sistema] B[Testes unitários] C[Testes de integração] D[Testes de contratos] E[CI] F[Deploy seguro] A --> B B --> C C --> D D --> E E --> F
Cada camada da suíte de testes aumenta nossa confiança nas alterações realizadas, permitindo que agentes trabalhem com maior autonomia sem comprometer a estabilidade do sistema.
Conclusão
A IA tornou o código barato de produzir, mas não tornou o software mais confiável. Essa responsabilidade continua sendo nossa.
Quanto maior a autonomia que damos aos agentes, mais explícitos precisam ser os contratos que definem o comportamento esperado do software. A suíte de testes deixa de ser apenas uma ferramenta de validação e passa a ser um dos principais mecanismos de governança do desenvolvimento assistido por IA.
Na Barte, isso deixou de ser teoria, como exemplo, no último ano aumentamos a cobertura de testes do nosso monólito de cerca de 20% para mais de 60% de instrução, com o número de testes crescendo cerca de 10× (de ~500 para mais de 5.000) — boa parte disso vindo da construção de uma suíte de testes de integração que antes praticamente não existia. E isso não é um esforço isolado: essa suíte é uma das peças do caminho pavimentado que estamos construindo na Barte, o mesmo que torna seguro delegar cada vez mais para os agentes e aumentar a confiança das nossas entregas.
Escrever código deixou de ser a parte mais difícil do desenvolvimento de software. Confiar que ele continua fazendo exatamente o que deveria fazer nunca foi tão importante.