Testes com IA
Ângulo 1 — Usar LLMs para gerar e assistir testes
Seção intitulada “Ângulo 1 — Usar LLMs para gerar e assistir testes”O que funciona bem
Seção intitulada “O que funciona bem”- Boilerplate e setup: montar
@BeforeEach, fixtures, dados de exemplo, conversões de DTO — trabalho mecânico onde o LLM economiza tempo real. - Enumerar casos de borda: pedir “liste os casos de borda desta função” costuma render entradas que você não pensou (nulo, vazio, limites, overflow, negativos).
- Tabelas de
@ParameterizedTest: gerar@CsvSourcea partir de uma especificação textual. - Traduzir intenção em asserts AssertJ fluentes quando você já sabe o que verificar.
- Explicar um teste legado que ninguém entende antes de refatorá-lo.
Como pedir bem
Seção intitulada “Como pedir bem”- Dê a especificação, não o código: se você cola a implementação e pede “escreva testes”, o LLM tende a espelhar o código — inclusive seus bugs. Descreva o comportamento esperado e deixe o teste ser independente da implementação.
- Exija a stack e o estilo: “JUnit 5 + AssertJ, sem Mockito onde não for necessário, um comportamento por teste, nomes descritivos em português”.
- Peça o teste falhando primeiro (mentalidade TDD): “escreva o teste para este requisito que ainda não implementei”.
- Force os casos negativos: LLMs enviesam para o happy path; peça explicitamente exceções, limites e entradas inválidas.
- Itere em passos pequenos: um comportamento por vez, revisando cada bloco.
Armadilhas (o que o LLM produz de ruim)
Seção intitulada “Armadilhas (o que o LLM produz de ruim)”- Testes tautológicos: asserts que sempre passam (
isNotNull, ou reafirmar exatamente o que o mock foi mandado retornar). - Falsa cobertura: exercita linhas sem verificar nada relevante — cobertura sobe, confiança não (ver Fundamentos da Automação de Testes).
- Asserts fracos:
assertThat(lista).isNotEmpty()onde o correto seriacontainsExactly(...). - Supermocking: mocka objetos de domínio que deveriam ser reais, testando a fiação (ver Test Doubles - Stub, Spy, Mock e Fake).
- Espelhar o bug: ao gerar teste a partir do código, o LLM assume que o código está certo e cristaliza o defeito como “esperado”.
Exemplo típico de teste tautológico gerado por IA — passa mesmo com a lógica errada:
@Testvoid calculaImposto() { var calc = new CalculadoraImposto(); when(tabela.aliquota(any())).thenReturn(new BigDecimal("0.10"));
BigDecimal r = calc.calcular(new BigDecimal("100"), tabela);
assertThat(r).isNotNull();}Correção — assert que fixa o valor esperado, derivado da regra e não da implementação:
@Testvoid aplicaAliquotaDeDezPorCentoSobreABase() { var calc = new CalculadoraImposto(); when(tabela.aliquota(any())).thenReturn(new BigDecimal("0.10"));
BigDecimal r = calc.calcular(new BigDecimal("100"), tabela);
assertThat(r).isEqualByComparingTo("10.00");}Ângulo 2 — Revisar criticamente testes escritos por IA
Seção intitulada “Ângulo 2 — Revisar criticamente testes escritos por IA”Trate código de teste gerado como PR de um júnior apressado: útil, mas culpado até prova em contrário. Checklist de revisão:
- O assert verifica comportamento real? Nada de
isNotNullsolitário; o valor esperado está literal e correto? - O teste falharia se a regra quebrasse? Faça o teste mental: altere a implementação de propósito — o teste pega? (Formalize isso com mutation testing, abaixo.)
- O esperado foi derivado da especificação ou copiado do código? Se copiado, ele apenas congela o comportamento atual, bug incluso.
- Há mock demais? Objetos de valor/domínio deveriam ser reais.
- Só verifica interação onde estado bastaria? Prefira verificação de estado (mais resistente a refactoring).
- Um comportamento por teste? Ou o LLM empilhou cinco asserts sem relação?
- Nome descreve a regra?
test1/testCalcularnão passam. - É determinístico? Sem dependência de tempo, ordem, locale (Princípio FIRST).
- Cobre os negativos? Exceções, nulos, limites — não só o happy path.
- Está na camada certa? O LLM às vezes sobe
@SpringBootTestpara o que seria unitário puro (Tipos de Teste - Unit, Integration e E2E).
Ângulo 3 — Reforçar a suíte além da IA
Seção intitulada “Ângulo 3 — Reforçar a suíte além da IA”Duas técnicas medem e ampliam o poder da suíte de forma objetiva, independente de quem escreveu os testes.
Mutation testing (PIT / PITest)
Seção intitulada “Mutation testing (PIT / PITest)”Mutation testing responde à pergunta que a cobertura não responde: “meus testes de fato pegam bugs?”. A ferramenta introduz pequenas mutações no código de produção (troca + por -, > por >=, remove uma chamada, nega uma condição) e roda a suíte. Se algum teste falha, o mutante foi “morto” — bom. Se a suíte continua verde, o mutante sobreviveu — há um buraco: aquela linha está coberta mas não verificada.
O mutation score (mutantes mortos / total) é uma métrica de qualidade muito mais honesta que a cobertura de linha, e expõe exatamente os testes tautológicos que uma IA (ou um humano) tende a produzir.
<plugin> <groupId>org.pitest</groupId> <artifactId>pitest-maven</artifactId> <version>1.16.1</version> <configuration> <targetClasses> <param>com.exemplo.dominio.*</param> </targetClasses> <targetTests> <param>com.exemplo.dominio.*Test</param> </targetTests> </configuration> <dependencies> <dependency> <groupId>org.pitest</groupId> <artifactId>pitest-junit5-plugin</artifactId> <version>1.2.1</version> </dependency> </dependencies></plugin>mvn org.pitest:pitest-maven:mutationCoverageO teste tautológico do imposto (com isNotNull) daria cobertura de linha alta e mutation score baixíssimo: qualquer mutação na fórmula do cálculo (trocar multiply por add, negar uma condição, remover uma chamada) passaria despercebida, porque o assert nunca olha o valor. PIT delataria imediatamente.
Property-based testing (jqwik)
Seção intitulada “Property-based testing (jqwik)”Em vez de exemplos fixos, você declara propriedades invariantes que devem valer para toda entrada, e o framework gera centenas de casos aleatórios (incluindo bordas que ninguém lembra) tentando falsificar a propriedade. Ao achar um contraexemplo, faz shrinking — reduz ao menor caso que ainda quebra.
class OrdenacaoProperties {
@Property void ordenarNaoAlteraTamanho(@ForAll List<@IntRange(min = -1000, max = 1000) Integer> lista) { List<Integer> ordenada = Ordenador.crescente(lista);
assertThat(ordenada).hasSameSizeAs(lista); }
@Property void resultadoFicaEmOrdemNaoDecrescente(@ForAll List<Integer> lista) { List<Integer> ordenada = Ordenador.crescente(lista);
assertThat(ordenada).isSorted(); }
@Property void ordenarEIdempotente(@ForAll List<Integer> lista) { List<Integer> uma = Ordenador.crescente(lista); List<Integer> duas = Ordenador.crescente(uma);
assertThat(duas).isEqualTo(uma); }}Propriedades boas são as invariantes do domínio: round-trip (decode(encode(x)) == x), comutatividade, idempotência, preservação de tamanho/soma. Elas cobrem um espaço de entradas que exemplos manuais — e listas geradas por IA — jamais alcançam.
Como as três peças se encaixam
Seção intitulada “Como as três peças se encaixam”graph LR
IA["IA gera rascunho de testes"] --> REV["Humano revisa criticamente"]
REV --> PIT["PIT mede se os testes pegam bugs"]
PIT --> PBT["jqwik amplia o espaço de entradas"]
PIT -.mutantes vivos.-> REV
A IA acelera a escrita; a revisão humana filtra o ruído; o mutation testing mede objetivamente a qualidade e expõe o que a IA maquiou; o property-based amplia a cobertura de espaço de entrada. Nenhuma substitui as outras.
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”- Aceitar teste de IA sem revisar: é a via expressa para falsa cobertura em escala.
- Gerar testes a partir do código de produção: cristaliza bugs como comportamento esperado; parta da especificação.
- Usar cobertura de linha para “provar” que os testes de IA são bons: cobertura não mede força de assert; use mutation score.
- Confiar no happy path que o LLM prioriza: force casos negativos e limites explicitamente.
- Property-based com propriedade fraca (
assertThat(r).isNotNull()para toda entrada): mesma tautologia, agora com 1000 execuções. - Deixar mutation testing rodar na suíte inteira sempre: é caro; foque no núcleo de domínio e rode em pipeline dedicado.
Relações
Seção intitulada “Relações”- Testes e TDD
- Fundamentos da Automação de Testes
- Princípio FIRST
- Tipos de Teste - Unit, Integration e E2E
- Test-Driven Development
- Test Doubles - Stub, Spy, Mock e Fake
- Testabilidade e Arquitetura
Perguntas de revisão
Seção intitulada “Perguntas de revisão”1. Por que gerar testes a partir do código de produção é arriscado?
Resposta
Porque o LLM assume que o código está correto e escreve asserts que espelham o comportamento atual — bugs incluídos — como se fossem o esperado. O teste então apenas congela a implementação e nunca detectaria o defeito. Deve-se partir da especificação de comportamento, não do código.
2. O que o mutation testing mede que a cobertura de linha não mede?
Resposta
A capacidade real dos testes de detectar bugs. Ele muta o código e verifica se algum teste falha; mutantes sobreviventes indicam linhas cobertas mas não verificadas. O mutation score expõe testes tautológicos que a cobertura de linha aprovaria.
3. Cite três itens do checklist para revisar um teste escrito por IA.
Resposta
Por exemplo: (1) o assert verifica um valor concreto derivado da especificação, não isNotNull; (2) o teste falharia se a regra fosse quebrada de propósito; (3) não há supermocking de objetos de domínio. Outros válidos: um comportamento por teste, determinismo, cobertura de casos negativos, camada correta.
4. O que caracteriza uma boa propriedade em property-based testing?
Resposta
Uma invariante que deve valer para toda entrada do domínio — round-trip (decode(encode(x)) == x), idempotência, comutatividade, preservação de tamanho ou soma. Propriedades fracas como “resultado não nulo” são tautológicas mesmo executadas mil vezes; a força vem de afirmar uma relação real que um contraexemplo poderia falsificar.