7 min

Prompt do Claude Code para geração de testes de casos-limite

Aprenda um prompt do Claude Code que gera testes de alto sinal ao mirar limites, invariantes e modos de falha em vez dos caminhos felizes.

Prompt do Claude Code para geração de testes de casos-limite

Por que gerar apenas happy-paths é perda de tempo

Suites de testes auto-geradas costumam impressionar: dezenas de testes, muito código de setup, e todo nome de função aparece em algum lugar. Mas muitos desses testes são apenas checagens de “funciona quando tudo está normal”. Eles passam fácil, raramente pegam bugs e ainda custam tempo para ler e manter.

Com um prompt típico do Claude Code para geração de testes, o modelo tende a espelhar os exemplos que recebe. Você obtém variações que parecem diferentes, mas cobrem o mesmo comportamento. O resultado é uma grande suíte com cobertura rasa onde importa.

Testes de alto sinal são diferentes. São o pequeno conjunto que teria pego o incidente do mês passado. Falham quando o comportamento muda de forma arriscada e permanecem estáveis quando ocorrem refactors inofensivos. Um teste de alto sinal pode valer vinte checagens de “retorna o valor esperado”.

Geração de happy-path de baixo valor costuma ter alguns sinais claros:

  • Muitos testes diferem só em rótulos de entrada, não no que pode quebrar.
  • Asserções são rasas (“não nulo”, “status é 200”) em vez de checar significado.
  • O setup pesa mais que o comportamento testado, então as pessoas param de atualizar os testes.
  • A cobertura parece alta, mas casos extremos ficam intocados.

Imagine uma função que aplica um código de desconto. Testes de happy-path confirmam que “SAVE10” reduz o preço. Bugs reais se escondem em outro lugar: preços 0 ou negativos, cupons expirados, arestas de arredondamento, ou limites máximos de desconto. Esses são os casos que causam totais errados, clientes irritados e reversões à meia-noite.

O objetivo é passar de “mais testes” para “testes melhores” mirando três alvos: limites, modos de falha e invariantes.

Os três alvos: limites, modos de falha, invariantes

Se você quer testes unitários de alto sinal, pare de pedir “mais testes” e comece a pedir três tipos específicos. Este é o núcleo de um prompt Claude Code que produz cobertura útil em vez de um monte de checagens “funciona com entrada normal”.

1) Limites (onde bugs se escondem)

Limites são as bordas do que o código aceita ou produz. Muitos defeitos reais são off-by-one, estados vazios ou problemas de timeout que nunca aparecem no happy path.

Pense em mínimos e máximos (0, 1, comprimento máximo), vazio vs presente ("", [], nil), off-by-one (n-1, n, n+1) e limites de tempo (perto do corte).

Exemplo: se uma API aceita “até 100 itens”, teste 100 e 101, não apenas 3.

2) Modos de falha (prove que falha de forma segura)

Modos de falha são as maneiras como o sistema pode quebrar: entradas ruins, dependências ausentes, resultados parciais ou erros a montante. Bons testes de modo de falha checam o comportamento sob estresse, não apenas a saída em condições ideais.

Exemplo: quando uma chamada ao banco falha, a função retorna um erro claro e evita gravar dados parciais?

3) Invariantes (regras que devem sempre valer)

Invariantes são verdades que devem permanecer verdade antes e depois da chamada. Elas transformam correção vaga em asserções nítidas.

Exemplos:

  • “Saldo nunca fica negativo” após qualquer tentativa de saque.
  • “IDs são únicos” mesmo que você crie itens rapidamente.
  • “Em erro, nenhum estado é alterado” (sem novas linhas, sem flags invertidas).

Quando você foca nesses três alvos, tem menos testes, mas cada um carrega mais sinal.

Preparação: extraia um contrato pequeno antes de escrever testes

Se você pedir testes cedo demais, geralmente recebe um monte de checagens educadas de “funciona como esperado”. Uma solução simples é escrever um contrato curtinho primeiro e, então, gerar testes a partir desse contrato. É a maneira mais rápida de transformar um prompt Claude Code em algo que encontra bugs reais.

Um contrato útil é curto o suficiente para ser lido de uma só vez. Mire em 5 a 10 linhas que respondam três perguntas: o que entra, o que sai e o que mais muda.

Modelo de contrato de 5–10 linhas

Escreva o contrato em linguagem natural, não em código, e inclua só o que você pode testar.

  • Entradas: tipos, intervalos permitidos e o que conta como “vazio” ou “faltando”.
  • Saída: valor de retorno ou formato de erro, e o que a “sucesso” garante.
  • Efeitos colaterais: mudanças de estado, linhas no banco, chamadas de rede, arquivos, logs.
  • Assunções: coisas que chamadores frequentemente erram (timezone, encoding, auth, ordenação).
  • “Não pode acontecer”: crash, perda silenciosa de dados, cobrança dupla, gravações parciais.

Com isso em mãos, mapeie onde a realidade pode quebrar suas assunções. Esses pontos viram casos-limite (min/max, zero, overflow, strings vazias, duplicatas) e modos de falha (timeouts, permissão negada, violação de unicidade, input corrompido).

Aqui vai um exemplo concreto para uma feature como reserveInventory(itemId, qty):

O contrato poderia dizer que qty deve ser inteiro positivo, a função deve ser atômica e nunca criar estoque negativo. Isso imediatamente sugere testes de alto sinal: qty = 0, qty = 1, qty maior que o disponível, chamadas concorrentes e um erro forçado no banco no meio da operação.

Se você usa uma ferramenta tipo Koder.ai, o mesmo fluxo se aplica: escreva o contrato primeiro no chat e, depois, gere testes que ataquem diretamente limites, modos de falha e a lista de “não pode acontecer”.

Padrão de prompt: o blueprint para testes de alto sinal

Use este prompt Claude Code quando quiser menos testes, mas cada um com maior peso. O movimento chave é forçar um plano de testes primeiro e só então gerar código após aprovar o plano.

You are helping me write HIGH-SIGNAL unit tests.

Context
- Language/framework: <fill in>
- Function/module under test: <name + short description>
- Inputs: <types, ranges, constraints>
- Outputs: <types + meaning>
- Side effects/external calls: <db, network, clock, randomness>

Contract (keep it small)
1) Preconditions: <what must be true>
2) Postconditions: <what must be true after>
3) Error behavior: <how failures are surfaced>

Task
PHASE 1 (plan only, no code):
A) Propose 6-10 tests max. Do not include “happy path” unless it protects an invariant.
B) For each test, state: intent, setup, input, expected result, and WHY it is high-signal.
C) Invariants: list 3-5 invariants and how each will be asserted.
D) Boundary matrix: propose a small matrix of boundary values (min/max/empty/null/off-by-one/too-long/invalid enum).
E) Failure modes: list negative tests that prove safe behavior (no crash, no partial write, clear error).
Stop after PHASE 1 and ask for approval.

PHASE 2 (after approval):
Generate the actual test code with clear names and minimal mocks.

Um truque prático é exigir a matriz de limites como uma tabela compacta, para que lacunas fiquem óbvias:

DimensionValid edgeJust outside“Weird” valueExpected behavior
length0-110,000error vs clamp vs accept

Se Claude propõe 20 testes, peça para reduzir. Solicite mesclar casos semelhantes e manter só os que pegariam um bug real (off-by-one, tipo de erro errado, perda silenciosa de dados, invariante quebrada).

Passo a passo: execute o prompt e transforme a saída em testes

Put the blueprint to work
Use Koder.ai to turn the blueprint prompt into repeatable, reviewable testing habits.

Comece com um contrato pequeno e concreto para o comportamento que você quer. Cole a assinatura da função, uma curta descrição de entradas e saídas e testes existentes (mesmo que sejam só happy-path). Isso mantém o modelo ancorado no que o código realmente faz, não no que ele supõe.

Depois, peça uma tabela de riscos antes de pedir qualquer código de teste. Exija três colunas: casos-limite (bordas da entrada válida), modos de falha (entrada ruim, dados ausentes, timeouts) e invariantes (regras que devem sempre valer). Adicione uma frase por linha: “por que isso pode quebrar”. Uma tabela simples revela lacunas mais rápido que um monte de arquivos de teste.

Em seguida, escolha o menor conjunto de testes onde cada um tem um propósito único de pegar bugs. Se dois testes falham pelo mesmo motivo, mantenha o mais forte.

Uma regra prática de seleção:

  • Mantenha testes que atinjam diferentes limites (min, max, vazio, off-by-one).
  • Mantenha testes que provem comportamento seguro sob falha (erro claro, sem gravação parcial, sem crash).
  • Mantenha testes que afirmem uma invariante (ordenação, totais, idempotência, sem duplicatas).
  • Corte testes que só repetem “funciona com entrada normal”.

Por fim, exija uma curta explicação por teste: qual bug ele pegaria se falhasse. Se a explicação for vaga (“valida comportamento”), o teste provavelmente é de baixo sinal.

Como codificar invariantes em asserções

Uma invariante é uma regra que deve permanecer verdadeira para qualquer entrada válida. Com testes baseados em invariantes, primeiro escreva a regra em linguagem natural e depois transforme-a em uma asserção que possa falhar ruidosamente.

Escolha 1 ou 2 invariantes que realmente te protejam de bugs reais. Boas invariantes costumam ser sobre segurança (sem perda de dados), consistência (mesma entrada = mesma saída) ou limites (nunca exceder caps).

Transforme uma invariante em uma checagem que você pode provar

Escreva a invariante em uma frase curta, depois decida que evidência seu teste pode observar: valores de retorno, dados armazenados, eventos emitidos ou chamadas a dependências. Asserções fortes checam tanto resultado quanto efeitos colaterais, porque muitos bugs se escondem em “retornou OK, mas gravou errado”.

Por exemplo, se você tem uma função que aplica um cupom ao pedido:

  • Invariante: o total final nunca é negativo.
  • Invariante: aplicar o mesmo cupom duas vezes não desconta duas vezes.

Agora codifique isso em asserções que medem algo concreto:

expect(result.total).toBeGreaterThanOrEqual(0)
expect(db.getOrder(orderId).discountCents).toBe(originalDiscountCents)

Evite asserts vagos como “retorna o resultado esperado”. Afirme a regra específica (não-negativo) e o efeito colateral específico (desconto armazenado uma vez).

Adicione uma nota de contraexemplo para manter o teste afiado

Para cada invariante, acrescente uma nota curta no teste sobre que dado a viola. Isso evita que o teste vire um happy-path com o tempo.

Um padrão simples que se mantém com o tempo:

  • Coloque a invariante no nome do teste.
  • Asserte a invariante no resultado.
  • Asserte o efeito colateral chave (ou a ausência dele).
  • Adicione um comentário com um caso que violaria a invariante (por exemplo, um cupom enorme ou aplicação duplicada).

Modos de falha: escreva testes que provem comportamento seguro

Testes de alto sinal frequentemente confirmam que seu código falha de forma segura. Se um gerador só escreve testes de happy-path, você quase não aprende nada sobre o comportamento quando entradas e dependências ficam bagunçadas.

Comece decidindo o que “falhar de forma segura” significa para a feature. Retorna um erro tipado? Cai para um padrão? Tenta novamente uma vez e para? Escreva esse comportamento esperado em uma frase e faça os testes provarem isso.

Ao pedir ao Claude Code por testes de modo de falha, mantenha o objetivo estrito: cubra as formas como o sistema pode quebrar e afirme a resposta exata que você quer. Uma linha útil é: “Prefira menos testes com asserções mais fortes em vez de muitos testes rasos.”

Categorias de falha que tendem a gerar bons testes:

  • Entradas ruins: formatos inválidos, campos obrigatórios ausentes, valores fora do intervalo
  • Falhas de dependência: timeouts, 500s, respostas vazias, payloads corrompidos
  • Problemas de ordenação: eventos fora de ordem, duplicatas, gravações parciais
  • Concorrência: updates em corrida, checagens de idempotência
  • Comportamento de recuperação: quando você retorna erro vs cair para um fallback vs retry

Exemplo: um endpoint cria um usuário e chama um serviço de email para mandar boas-vindas. Um teste de baixo valor checa “retorna 201”. Um teste de alto sinal pergunta: se o serviço de email timeoute, você (a) ainda cria o usuário e retorna 201 com uma flag “email_pending”, ou (b) retorna 503 e não cria o usuário? Escolha um comportamento e afirme resposta e efeitos colaterais.

Também teste o que você não deve vazar. Se a validação falha, certifique-se de que nada foi escrito no banco. Se uma dependência retorna payload corrompido, assegure que você não lança uma exceção não tratada nem retorna stack traces brutos.

Armadilhas comuns que geram testes de baixo valor

Build and test in one flow
Create your app and generate boundary and failure tests as you build features.

Conjuntos de testes de baixo valor geralmente surgem quando o modelo é recompensado por volume. Se seu prompt pede “20 unit tests”, você costuma receber pequenas variações que parecem completas, mas não pegam nada novo.

Armadilhas comuns:

  • Testes parecidos: o mesmo teste de “entrada válida” repetido com strings ou números diferentes.
  • Testes que espelham o código: afirmando passos privados ou helpers em vez de comportamento observável.
  • Mockar tudo: substituir DB, clock, rede e config ao mesmo tempo.
  • Asserções fracas: checar só “sem erro”, “não nulo” ou “status 200”.
  • Estado compartilhado sujo: deixar dados seedados, globals modificadas ou caches ativos.

Exemplo: uma função “create user”. Dez testes de happy-path podem variar a string do email e ainda perder o importante: rejeitar emails duplicados, tratar senha vazia e garantir que IDs retornados sejam únicos e estáveis.

Diretrizes que ajudam na revisão:

  • Exija que cada teste nomeie o risco que cobre (limite, modo de falha ou invariante).
  • Evite checagens puramente de implementação a menos que mudem comportamento observável.
  • Mantenha mocks ao mínimo, e permita alguns testes que batam no ponto de integração real quando factível.
  • Exija asserções fortes: outputs exatos, mudanças de estado e tipos/mensagens de erro.
  • Adicione regras de limpeza para que testes não dependam de ordem.

Exemplo: transformar uma feature em um conjunto pequeno e forte de testes

Imagine a feature de aplicar um cupom no checkout.

Contrato (pequeno e testável): dado um subtotal do carrinho em centavos e um cupom opcional, retorne um total final em centavos. Regras: cupons percentuais arredondam para baixo até o centavo mais próximo, cupons fixos subtraem um valor fixo e totais nunca ficam abaixo de 0. Um cupom pode ser inválido, expirado ou já usado.

Não peça “tests for applyCoupon()”. Peça testes de casos-limite, modos de falha e invariantes ligados a esse contrato.

Limites para forçar comportamento de borda

Escolha entradas que normalmente quebram matemática ou validação: string de cupom vazia, subtotal = 0, subtotal logo abaixo e acima de um gasto mínimo, desconto fixo maior que o subtotal, e uma porcentagem como 33% que gera arredondamento.

Modos de falha para provar comportamento seguro

Assuma que a busca de cupom pode falhar e o estado pode estar errado: serviço de cupom fora, cupom expirado ou cupom já resgatado pelo usuário. O teste deve provar o que acontece a seguir (cupom rejeitado com erro claro, total sem alteração).

Um conjunto mínimo e de alto sinal (5 testes) e o que cada um pega:

  • Rejeitar código vazio ou só espaços: pega bugs que aceitam blank como válido e problemas de trim.
  • Cupom percentual e arredondamento (subtotal 101, 33%): pega erros de arredondamento e off-by-one de centavos.
  • Desconto fixo maior que o subtotal (subtotal 500, desconto 1000): prova a invariante de que total nunca fica negativo.
  • Limite de gasto mínimo (subtotal 999 vs 1000): pega lógica de comparação errada (< vs <=).
  • Falha na busca do cupom ou timeout: prova fallback seguro (nenhum desconto aplicado) e tratamento de erro estável.

Se esses passarem, você cobriu os pontos de quebra comuns sem encher a suíte com testes duplicados de happy-path.

Checklist rápido para testes gerados por IA de alto sinal

Write high-signal tests faster
Turn your contract into a focused test plan inside one chat-based build space.

Antes de aceitar o que o modelo gera, faça uma revisão rápida. O objetivo é testes que, individualmente, te protejam de um bug específico e provável.

Use este checklist como porta de qualidade:

  • Limites por entrada: para cada campo (strings, IDs, timestamps, flags), inclua pelo menos um caso-limite (vazio vs só espaços, comprimento máximo, zero vs negativo, campos opcionais faltando, um além do limite).
  • Falhas de dependência: inclua pelo menos um teste onde uma dependência se comporta mal (timeout de DB, API terceira 500, token expirado). Prove comportamento seguro (erro claro, sem gravações parciais).
  • Invariantes com asserções fortes: escolha 1–3 regras que sempre devem valer e afirme-as diretamente. Evite asserts vagos como “response is ok”.
  • Um bug único por teste: leia cada título e pergunte “Que bug exato isso pegaria?” Se dois testes respondem a mesma pergunta, una-os.
  • Teste de remoção: tente deletar um teste. Se nada significativo se perde (nenhum limite, modo de falha ou invariante), ele não merecia ficar.

Um truque prático após a geração: renomeie testes para “should <comportamento> when <condição de borda>” e “should not <resultado ruim> when <falha>”. Se não der para renomear com clareza, o teste não está focado.

Se você constrói com Koder.ai, esse checklist também funciona bem com snapshots e rollback: gere testes, rode-os e reverta se o novo conjunto só adicionou ruído sem melhorar cobertura.

Próximos passos: torne isso um fluxo repetível

Trate seu prompt como uma armação reutilizável, não um pedido pontual. Salve um blueprint (aquele que força limites, modos de falha e invariantes) e reutilize para cada nova função, endpoint ou fluxo de UI.

Um hábito simples que melhora resultados rápido: peça uma frase por teste explicando qual bug ele pegaria. Se a frase for genérica, o teste é provavelmente ruído.

Mantenha uma lista viva de invariantes de domínio para seu produto. Não guarde na cabeça. Acrescente sempre que encontrar um bug real.

Um fluxo leve que você pode repetir:

  • Extraia um contrato pequeno: entradas, saídas, tratamento de erro e 3–5 invariantes.
  • Rode o prompt blueprint e peça limites, modos de falha, invariantes e justificativas de uma linha.
  • Implemente só os 5–10 testes principais que cobrem riscos distintos.
  • Refatore, então rode o prompt novamente para ver novos riscos.
  • Apure duplicatas e mantenha testes que teriam pego incidentes passados.

Se você constrói apps via chat, rode este ciclo dentro do Koder.ai (koder.ai) para que contrato, plano e testes gerados vivam em um só lugar. Quando um refactor muda comportamento inesperadamente, snapshots e rollback tornam mais fácil comparar e iterar até que seu conjunto de alto sinal se mantenha estável.

Perguntas frequentes

Quantos testes unitários devo gerar por função?

Padrão: vise um conjunto pequeno que realmente pegaria um bug.

Um limite prático que funciona bem é 6–10 testes por unidade (função/módulo). Se você precisa de mais, provavelmente sua unidade está fazendo coisas demais ou o contrato não está claro.

O que há de errado em gerar muitos testes de happy-path?

Testes de happy-path normalmente só provam que seu exemplo ainda funciona. Eles tendem a perder os problemas que aparecem em produção.

Testes de alto sinal focam em:

  • Limites (0/1/max, vazio/null, off-by-one)
  • Modos de falha (timeouts, entradas inválidas, erros de dependência)
  • Invariantes (regras que sempre devem valer, como “sem gravação parcial em erro”)
O que devo escrever antes de pedir para a IA gerar testes?

Comece com um contrato pequeno que caiba em uma frase:

  • Entradas: tipos, intervalos permitidos, o que conta como vazio/faltando
  • Saídas: formato de sucesso e de erro
  • Efeitos colaterais: o que pode ser escrito/modificado (BD, arquivos, rede)
  • “Não pode acontecer”: crash, perda silenciosa de dados, cobrança dupla, gravações parciais

Depois, gere testes a partir desse contrato, não apenas a partir de exemplos.

Quais casos-limite geralmente valem a pena testar?

Teste estes primeiro:

  • Valores mínimo/máximo (0, 1, max, max+1)
  • Vazio vs presente ("", [], null/nil)
  • Off-by-one (n-1, n, n+1)
  • Bordas de formatação (strings só com espaços, zeros à esquerda)
  • Cortes de tempo (logo antes/depois do vencimento)

Escolha uma ou duas por dimensão de entrada para que cada teste cubra um risco único.

Como escrever um bom teste de “modo de falha” em vez de um raso?

Um bom teste de modo de falha prova duas coisas:

  1. A função retorna um erro claro e esperado (tipo/mensagem/status).
  2. Ela falha de forma segura:
  • sem gravações parciais
  • sem vazamento de detalhes internos
  • sem retries ou efeitos colaterais indesejados

Se há escrita no banco, sempre verifique o que aconteceu no armazenamento depois da falha.

Como transformar uma invariante em uma asserção de teste?

Abordagem padrão: transforme a invariante em uma asserção sobre resultados observáveis.

Exemplos:

  • “Total nunca negativo” → expect(total).toBeGreaterThanOrEqual(0)
  • “Em erro, sem mudanças” → verificar nenhuma linha nova / nenhuma flag alterada
  • “Idempotente” → chamar duas vezes e afirmar que a segunda chamada não mudou o estado

Prefira checar tanto valor retornado quanto efeitos colaterais, porque muitos bugs aparecem quando a função “retorna OK, mas gravou errado”.

Quando um teste de happy-path ainda vale a pena?

Vale manter um teste de happy-path quando ele protege uma invariante ou uma integração crítica.

Bons motivos para manter um:

  • Afirma uma invariante chave em entrada normal (ex.: regras de arredondamento)
  • Trava um contrato de API que consumidores dependem
  • Protege contra uma regressão que já aconteceu

Caso contrário, prefira testes de limite/falha que peguem mais classes de bugs.

O que devo pedir ao modelo antes de gerar código de teste?

Insista na FASE 1: apenas plano primeiro.

Exija que o modelo entregue:

  • 6–10 testes propostos no máximo
  • Para cada um: intenção, setup, entrada, resultado esperado, por que é de alto sinal
  • Uma pequena matriz de limites
  • Uma lista de modos de falha
  • 3–5 invariantes e como afirmá-las

Só depois de aprovar o plano peça o código. Isso evita saída do tipo “20 testes parecidos”.

Como evitar testes frágeis porque mockam demais?

Padrão: simule apenas o limite que você não controla (BD/rede/clock) e mantenha o resto real.

Para evitar mocking demais:

  • Não simule helpers internos só para espelhar a implementação
  • Use uma versão em memória realista quando possível, ou um fake pequeno com comportamento claro
  • Simule o clock/aleatoriedade só quando isso afetar a asserção

Se um teste quebra após um refactor mas o comportamento não mudou, provavelmente está excessivamente atrelado à implementação ou mockado demais.

Como saber rapidamente se um teste gerado por IA é de baixo valor?

Use um teste simples de remoção:

  • Se você apagar o teste e não perder nenhum limite, nenhum modo de falha nem nenhuma invariante, ele não merecia estar lá.

Também procure duplicatas:

  • Se dois testes falham pelo mesmo bug, mantenha o que tem a asserção mais forte.
  • Se asserções são só “não nulo” ou “status 200”, fortaleça ou remova o teste.

Related posts