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.

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:
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.
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”.
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.
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?
Invariantes são verdades que devem permanecer verdade antes e depois da chamada. Elas transformam correção vaga em asserções nítidas.
Exemplos:
Quando você foca nesses três alvos, tem menos testes, mas cada um carrega mais sinal.
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.
Escreva o contrato em linguagem natural, não em código, e inclua só o que você pode testar.
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”.
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:
| Dimension | Valid edge | Just outside | “Weird” value | Expected behavior |
|---|---|---|---|---|
| length | 0 | -1 | 10,000 | error 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).
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:
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.
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).
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:
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).
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:
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:
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.
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:
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:
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.
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.
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:
Se esses passarem, você cobriu os pontos de quebra comuns sem encher a suíte com testes duplicados de happy-path.
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:
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.
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:
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.
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.
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:
Comece com um contrato pequeno que caiba em uma frase:
Depois, gere testes a partir desse contrato, não apenas a partir de exemplos.
Teste estes primeiro:
Um bom teste de modo de falha prova duas coisas:
Se há escrita no banco, sempre verifique o que aconteceu no armazenamento depois da falha.
Abordagem padrão: transforme a invariante em uma asserção sobre resultados observáveis.
Exemplos:
expect(total).toBeGreaterThanOrEqual(0)Vale manter um teste de happy-path quando ele protege uma invariante ou uma integração crítica.
Bons motivos para manter um:
Caso contrário, prefira testes de limite/falha que peguem mais classes de bugs.
Insista na FASE 1: apenas plano primeiro.
Exija que o modelo entregue:
Só depois de aprovar o plano peça o código. Isso evita saída do tipo “20 testes parecidos”.
Padrão: simule apenas o limite que você não controla (BD/rede/clock) e mantenha o resto real.
Para evitar mocking demais:
Se um teste quebra após um refactor mas o comportamento não mudou, provavelmente está excessivamente atrelado à implementação ou mockado demais.
Use um teste simples de remoção:
Também procure duplicatas:
Escolha uma ou duas por dimensão de entrada para que cada teste cubra um risco único.
Prefira checar tanto valor retornado quanto efeitos colaterais, porque muitos bugs aparecem quando a função “retorna OK, mas gravou errado”.