Teste Assistido por IA: Usando a Portão de Testes Unitários para Conter o Código Gerado por IA
Categories:
O que o teste assistido por IA resolve não é a cobertura de testes, mas sim minha confiança no código gerado pela IA. A IA pode gerar um trecho de código enorme em três minutos, enquanto uma pessoa leva duas horas para revisá-lo por completo — gerar em três minutos e revisar em duas horas não é exagero. Minha solução é mudar o objeto de revisão: em vez de ler linha por linha o código, leio o documento de casos de teste e, em seguida, construo uma portão no ambiente de desenvolvimento local usando testes unitários, deixando que os casos de teste falharem monitorem a preguiça e os erros da IA.
Pequenos bugs são o principal campo de batalha do teste assistido por IA
Os bugs se dividem em dois tipos: bugs fáceis de resolver e bugs difíceis de resolver.
Os bugs difíceis de resolver geralmente originate em defeitos de arquitetura ou design de solução, evoluindo gradualmente para uma dívida técnica, onde uma mudança afeta todo o sistema; ou podem ser causados por terceiros, como bibliotecas, sistemas operacionais ou compiladores, exigindo conhecimento mais profundo e exploração aprofundada para serem resolvidos. Esse tipo de problema complexo não está no escopo deste artigo.
Este artigo concentra-se nos pequenos bugs do ciclo de vida do produto, com a ajuda da IA para criar casos de teste, abordando os seguintes problemas:
- Bugs que podem ser descobertos rapidamente
- Bugs simples que exigem apenas uma correção mínima
- Bugs introduzidos por modificações
- Erros cometidos durante correções de emergência sob pressão
- O equilíbrio entre custo e eficiência de testes direcionados
A maioria dos bugs são pequenos, mas pequenos bugs podem causar sérios problemas. Resolver pequenos bugs localmente economiza uma quantidade significativa de energia subsequente.
Mudando o objeto de revisão do código para o documento de casos de teste
Uma vez tentei a ideia mais ousada: deixar a IA gerar um requisito por completo, sem revisar nenhuma linha do código gerado, similar ao modo Copilot Agent de deixar tudo livre. Acabei abandonando. A razão fundamental é a falta de segurança. Para um programa binário com lógica rigorosa e clareza entre 0 e 1, não ter segurança significa confiar sem verificação, o que perde a confiança de upstream e downstream, e a confiança do cliente. Portanto, estou aprendendo a pensar sobre o vibe coding: para o código gerado pela IA, preciso ter certeza.
O problema está no custo da revisão. Às vezes, a quantidade de código gerado ou modificado pela IA é enorme — gerar em três minutos e revisar em duas horas não é exagero. O trabalho de escrever testes geralmente excede o do código de negócios, e nem sempre é contado no indicator de entrega do desenvolvedor, o que é uma das razões pelas quais as pessoas evitam escrever testes ativamente. Mas a IA é muito mais inteligente do que eu é um fato, e em vez de resistir, é melhor dar a ela uma portão.
Mudar o objeto de revisão do código para o documento de casos de teste é a-chave para quebrar esse impasse. Diferente do código de negócios, os casos de teste gerados pela IA geralmente descrevem um cenário muito específico, fácil de revisar e verificar. Muitos casos de teste nem precisam ser lidos linha por linha — basta esperar que errem e examinar quando isso acontece. Dizer que não se revisa nenhuma linha do código da IA é um exagero, mas com uma documentação de casos de teste bem elaborada, pode-se provar que não é necessário uma revisão humana para cada linha de código.
---
title: De rever código para rever documentação
---
flowchart TB
A[IA gera muito código] e1@--> B[Custo de revisão linha por linha alto]
B e2@--> C{O que revisar}
C e3@-->|Código| D[Duas horas de revisão, sem segurança]
C e4@-->|Documento de casos de teste| E[Fácil de revisar, verificação rápida]
E e5@--> F[Portão de testes unitários]
F e6@--> G[Caso de teste falhante expõe o problema]
classDef risk fill:#FFF3E0,stroke:#E65100,color:#E65100;
classDef ok fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20;
classDef animate stroke:#EF6C00,stroke-width:2px,stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class D risk;
class E,F,G ok;
class e1,e2,e3,e4,e5,e6 animate;Base metodológica: Seis Sigma e DMAIC
Six Sigma é uma metodologia de melhoria de qualidade orientada por dados, cujo objetivo principal é controlar a taxa de defeitos em 3,4 por milhão, ou seja, nível 6σ, através da redução de variação de processo1.
Ela fornece um processo executível DMAIC:
- Define Definição: Análise de requisitos, transformação em modelo de dados, design de estruturas de dados e interfaces
- Measure Medição: Monitoramento de casos de teste, cobertura, desempenho, segurança
- Analyze Análise: Análise de casos de teste e código de negócios
- Improve Melhoria: Melhoria de casos de teste e código de negócios
- Control Controle: Portão de teste automatizado
As ferramentas técnicas associadas incluem linter, verificação estática, verificação dinâmica e frameworks de teste. Há muitos indicadores de qualidade de código que valem a pena, tais como indicadores de complexidade (complexidade ciclomática, complexidade cognitiva, profundidade aninhada, acoplamento de classe), indicadores de qualidade (taxa de duplicação, tamanho de função, números mágicos), e indicadores de teste (cobertura de testes unitários, taxa de aprovação de testes de integração, validade de casos de teste, cobertura de tratamento de exceções).
A metodologia e as ferramentas podem ajudar a descobrir e resolver problemas, mas o custo é um aumento potencial no custo de entrega e na eficiência. Com recursos limitados, é necessária uma abordagem que traga benefícios sustentáveis de curto, médio e longo prazo. Minha escolha é usar testes unitários como ferramenta auxiliar no processo de desenvolvimento.
Com a ajuda da IA na conversão entre diferentes linguagens de papéis, o desenvolvedor pode fusione as vantagens de TDD, BDD e ATDD, gerando rapidamente tabelas de casos de teste e códigos de casos de teste, melhorando a qualidade da entrega.
Teste orientado: TDD/BDD/ATDD e código testável
As diferenças entre os três tipos de orientação por teste são as seguintes:
| Dimensão | TDD | BDD | ATDD |
|---|---|---|---|
| Origem do impulso | Teste unitário | Cenários de comportamento do usuário | Critérios de aceitação |
| Participantes | Desenvolvedor | Negócios + desenvolvedor | Negócios + teste + desenvolvedor |
| Nível de teste | Teste unitário | Teste de integração/extrema-a-extrema | Teste de aceitação |
| Exemplos de ferramentas | JUnit, pytest | Cucumber, SpecFlow | FitNesse, Robot Framework |
| Saída típica | Relatório de cobertura de código | Documento de cenários de usuário executável | Taxa de aprovação de testes de aceitação |
| Linguagem | assert add(2,3) == 5 | Cenário em linguagem natural (Given-When-Then) | Linguagem de negócios (tabela de critérios de aceitação) |
O código testável tem quatro princípios centrais: responsabilidade única (cada função faz apenas uma coisa), injeção de dependência (passando dependências via parâmetros), isolamento de interface (tipos de entrada e saída claros), e observabilidade de estado (fornecendo uma interface de consulta para verificar o estado interno).
Na linha de prompt para a IA, eu recomendo escrever diretamente extensível, responsabilidade única, injeção de dependência. Uma vez tentei deixar a IA controlar o tamanho das funções e arquivos gerados, e seu cumprimento foi muito ruim; mas quando mudei para responsabilidade única e injeção de dependência, o código gerado ficou visivelmente mais alinhado com as expectativas. Otimização prematura é a raiz de todos os males — ao gerar estruturas de dados básicas e interfaces, é suficiente destacar a extensibilidade para a IA.
Exemplo de código fácil de testar:
// user.h
typedef struct {
int id;
char name[50];
int age;
} User;
// Interface abstrata de armazenamento em memória
typedef struct {
User* (*get_user)(int id);
int (*save_user)(const User* user);
} UserStorage;
// Criar usuário (injeção de dependência da interface de armazenamento)
User* create_user(const char* name, int age, UserStorage* storage) {
if(strlen(name) >= 50 || age <= 0) return NULL;
User* new_user = malloc(sizeof(User));
new_user->id = generate_user_id();
strncpy(new_user->name, name, 49);
new_user->name[49] = '\0';
new_user->age = age;
if(storage->save_user(new_user) != 0) {
free(new_user);
return NULL;
}
return new_user;
}
Exemplo de código difícil de testar:
// Acoplamento apertado com operações de banco de dados
void save_user_to_db(User* user) {
MYSQL conn;
mysql_init(&conn);
if(!mysql_real_connect(&conn, "localhost", "root", "pass", "test", 0, NULL, 0)) {
log_error("DB connection failed"); // Dependência de log difícil de testar
exit(1); // Sai diretamente, afetando a execução do teste
}
// Concatena SQL e escreve no banco de dados
}
Um bom código é silencioso e sutil; um ruim chama a atenção. Funções fáceis de testar têm valores de retorno claros, tratamento de erros padrão, parâmetros de entrada definidos, isolamento de efeitos colaterais, e suporte a injeção de dependência.
O código que a IA gera nem sempre é a melhor prática e tem problemas de estilo. Embora frequentemente resolva o problema, um projeto real não é uma única vez — é preciso considerar a manutenção futura. Quando pede à IA para gerar código, também é importante destacar legibilidade primeiro, extensível, responsabilidade única, injeção de dependência. Problemas de estilo de código não serão discutidos aqui; pode-se consulte o Guia de Estilo de Código do Google2.
Armadilhas e equilíbrios no desenvolvimento real
Durante a transferência entre papéis, os requisitos passam por múltiplas rodadas de conversão linguística, cada uma podendo introduzir desvios de formulação. Por exemplo, o que o cliente chama de “visualização centralizada” foi interpretado pelo backend como “retornar em uma única interface” sem fazer paginação. Usando a IA para gerar templates de linguagens de diferentes papéis, é possível reduzir a perda de informação na conversão manual.
A falta de consideração é a raiz de muitos bugs. Com a ajuda da IA para cenários de design de caso de teste, ela não apenas conclui a tarefa esperada, mas também fornece insights inesperados, preenchendo detalhes que o emissor do comando não considerou. Em cada rodada de conversão de linguagem de papel, é possível conversar com a IA para aprimorar falhas. Markdown é a linguagem que a IA entende mais facilmente — entrada e saída devem ser o mais possível no formato Markdown.
Vários pontos devem ser prestando atenção no código de caso de teste: isolamento de caso de teste, precisão da simulação de dependência, validade de asserção, valores de limite, tratamento de exceção, desempenho, concorrência. A IA é preguiçosa e não gerará casos de teste cobrindo todos os cenários de uma só vez. Gerar todo o código de teste de uma vez exige um modelo alto; é melhor gerar em múltiplas rodadas.
Tomando como exemplo o modelo Claude no Cursor, ele faz bem em alguns aspectos do código de caso de teste, mas frequentemente esquece o isolamento do caso de teste e os cenários de exceção. Adicionar uma regra do usuário pode reduzir um pouco a preguiça: ao gerar casos de teste, preste atenção à documentação de caso de teste e ao isolamento de caso de teste. No entanto, ainda não gerará todos os cenários de uma vez — caminhos normais, caminhos anormais, valores de limite, desempenho, concorrência e outros cenários precisam ser complementados em múltiplas conversas.
Um fluxo de trabalho viável de teste assistido por IA
Combinando com o documento de设计, é possível criar uma linha de produção que liga o cenário ao caso de teste:
---
title: Fluxo de trabalho de teste assistido por IA
---
flowchart TB
A[Documento de设计] e1@--> B[Gerar cenários de teste de aceitação]
B e2@--> C[Gerar estrutura de dados e interface]
C e3@--> D[Gerar tabela de caso de teste do lado do cliente]
D e4@--> E[Gerar código de caso de teste]
D e5@--> F[Gerar código de negócios]
E e6@--> G[Portão local: casos de teste aprovados]
F e7@--> G
G e8@--> H{Existe caso de teste falhante}
H e9@-->|Sim| E
H e10@-->|Não| I[Encaminhar para teste]
classDef input fill:#E3F2FD,stroke:#1565C0,color:#0D47A1;
classDef gen fill:#E8F5E9,stroke:#2E7D32,color:#1B5E20;
classDef gate fill:#FFF3E0,stroke:#E65100,color:#E65100;
classDef animate stroke:#EF6C00,stroke-width:2px,stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class A input;
class B,C,D,E,F gen;
class G,H,I gate;
class e1,e2,e3,e4,e5,e6,e7,e8,e9,e10 animate;Os prompts para cada etapa podem ser organizados assim: gerar cenários de teste de aceitação com base no documento de设计 do produto; gerar estruturas de dados e interfaces com base nos cenários de teste; gerar a tabela de caso de teste do lado do cliente com base nos cenários de teste e interfaces; gerar o código de caso de teste com base nas interfaces e na tabela de caso de teste; gerar o código de negócios com base nos cenários de teste, garantindo que os casos de teste passem.
Há aqui um perigo: a IA pode, dependendo do contexto, modify o código de negócios com base no código de teste, ou vice-versa. Se ambos os lados contiverem erros, a IA ficará confusa; à medida que o número de conversas aumenta, o código de teste e o código de negócios ficarão cada vez mais bagunçados — nesse momento, a intervenção humana é necessária.
Um exemplo de prompt ambicioso e inadequado: pedir à IA para cobrir normal, anormal, valores de limite, concorrência e gerar código de uma vez. Esse tipo de pedido tende a ser interrompido. A abordagem correta é dividir os cenários no Cursor e gerar em múltiplos pedidos separados. Casos de teste de desempenho exigem grandes quantidades de dados e devem ser colocados na fase de teste de sistema — primeiro pede à IA para gerar um gerador de dados aleatórios, e depois usa os dados gerados para construir os casos de teste.
Testes de integração exigem a construção de um ambiente de teste. Alguns casos de teste dependem de ferramentas externas, por exemplo, a micro-isolação depende de iptables e ipset, e pode-se usar Docker para construir um contêiner de teste. O princípio é: como uma pessoa testaria, comandar a IA para implementar os mesmos passos de automação. Quanto a testes de segurança, atualmente depende principalmente de experiência e análise, e ainda não há experiência madura de auxílio por IA.
Conclusão
Comparado com o fluxo de trabalho sem uso de IA, este fluxo produz mais documentação e código de teste, e é difícil revisar cada palavra. Mas a documentação é mais fácil de revisar do que o código, especialmente a tabela de caso de teste, que tem um guia preciso para a geração de casos de teste. No desenvolvimento real, por vezes reviso apenas os casos de teste falhantes, confirmando o problema ao depurar os casos de teste que falharam.
Três direções de melhoria: a sub-etapa de design tem cruzamento — os cenários de teste de aceitação podem depender da estrutura de dados e interface; a tabela de caso de teste do lado do cliente requer atenção especial do desenvolvedor; e antes do encaminhamento para teste, é necessário gerar novamente cenários de teste detalhados que incluam os detalhes do código de negócios.
O benefício final não está no número de cobertura de teste, mas na confiança: com a portão de testes unitários locais, finalmente posso ter certeza no código gerado pela IA, em vez de enviar sem pensamento o código gerado por vibe coding para a produção.
A definição de Six Sigma e o método DMAIC referem-se ao artigo da Wikipédia. ↩︎
O Guia de Estilo de Código do Google é o guia de estilo de código publicado pela Google. ↩︎