O que são testes negativos e testes de robustez
Testes negativos verificam como a API se comporta quando recebe entradas inválidas, incompletas, fora do formato esperado ou quando o cliente tenta executar ações proibidas. O objetivo não é “quebrar por quebrar”, e sim confirmar que a API falha de forma controlada: retorna códigos de status coerentes, mensagens de erro úteis (sem vazar detalhes sensíveis), e não deixa o sistema em um estado inconsistente.
Testes de robustez complementam os negativos ao explorar limites e condições extremas: valores máximos e mínimos, cargas de dados grandes, repetição de requisições, concorrência simulada e cenários de instabilidade. Uma API robusta mantém previsibilidade: responde com erros padronizados, respeita idempotência quando aplicável, e preserva consistência dos dados mesmo quando o cliente se comporta mal.
Erros: como validar códigos, mensagens e segurança do retorno
Mapeamento de classes de erro
Ao desenhar testes negativos, pense em classes de erro, não apenas em casos isolados. Em geral, você quer cobrir: erros de validação (entrada inválida), erros de autenticação/autorização (quando aplicável), recurso inexistente, conflito de estado, e falhas inesperadas do servidor. Em cada classe, o teste deve confirmar três coisas: o status code, o formato do corpo de erro e a ausência de vazamento de informação interna.
- 400 Bad Request: payload malformado, tipos inválidos, campos fora do padrão.
- 401/403: credenciais ausentes/invalidas ou permissão insuficiente (quando o endpoint exige).
- 404 Not Found: ID inexistente, rota inexistente (diferenciar quando possível).
- 409 Conflict: violação de unicidade, tentativa de criar recurso duplicado, conflito de versão.
- 422 Unprocessable Entity: validação semântica (ex.: e-mail válido, regras de negócio).
- 429 Too Many Requests: limites de rate limit.
- 500/502/503: falhas do servidor; o teste deve garantir que o erro é padronizado e não expõe stack trace.
Padronização do corpo de erro
Mesmo que a API não siga um padrão formal, é importante que os erros tenham estrutura consistente. Um exemplo comum é retornar um objeto com campos como code, message, details e um identificador de correlação (traceId). Em testes negativos, valide que o corpo existe, que a mensagem é compreensível e que details não inclui dados sensíveis (como SQL, caminhos internos, nomes de tabelas, stack traces completos).
// Exemplo de estrutura de erro esperada (ilustrativo) { "code": "VALIDATION_ERROR", "message": "Campo 'email' inválido", "details": [ { "field": "email", "reason": "format" } ], "traceId": "b3f1..." }Passo a passo prático: suíte de testes negativos para validação de payload
Objetivo: garantir que o endpoint de criação (por exemplo, POST /users) rejeita entradas inválidas com status e mensagens coerentes.
Continue em nosso aplicativo
Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.
ou continue lendo abaixo...Baixar o aplicativo
- Passo 1: crie uma pasta na Collection chamada “Negativos - Validação”.
- Passo 2: duplique a request de criação e crie variações: campo obrigatório ausente, tipo incorreto, string vazia, e-mail inválido, tamanho acima do limite.
- Passo 3: em cada variação, altere apenas o necessário no body para isolar a causa do erro.
- Passo 4: adicione testes para status code e para o formato do erro.
// Testes (aba Tests) - exemplo genérico para erro de validaçãopm.test("Deve retornar 400 ou 422", function () { pm.expect([400, 422]).to.include(pm.response.code);});pm.test("Deve retornar JSON com campos mínimos de erro", function () { const json = pm.response.json(); pm.expect(json).to.be.an("object"); pm.expect(json).to.have.property("message");});pm.test("Não deve vazar stack trace", function () { const text = pm.response.text(); pm.expect(text.toLowerCase()).to.not.include("exception"); pm.expect(text.toLowerCase()).to.not.include("stack"); pm.expect(text.toLowerCase()).to.not.include("select ");});Note que o teste de “não vazar stack trace” é heurístico. Ele não substitui revisão de segurança, mas ajuda a detectar regressões óbvias quando alguém altera o tratamento de erros.
Limites: tamanhos, faixas numéricas, paginação e rate limit
Testes de limites de campo (boundary testing)
Boundary testing foca nos valores “na borda”: mínimo permitido, máximo permitido, e um valor logo acima/abaixo. Para um campo como name com limite de 50 caracteres, você quer testar: 0 (vazio), 1, 50, 51. Para um campo numérico como quantity com mínimo 1 e máximo 999, teste: 0, 1, 999, 1000. A robustez aparece quando a API responde com erro claro e consistente, sem truncar silenciosamente, sem estourar exceções e sem aceitar valores inválidos.
Passo a passo prático: gerando strings grandes e validando resposta
Objetivo: validar que o servidor rejeita payloads com campos acima do limite e que não ocorre timeout ou erro 500.
- Passo 1: crie uma request “Negativo - name com 51 chars”.
- Passo 2: no body, gere uma string com 51 caracteres diretamente no JSON (ou via script, se preferir).
- Passo 3: valide que o status é 400/422 e que a mensagem aponta o campo correto.
// Exemplo de geração de string grande no Tests (ou Pre-request)const big = "a".repeat(51);pm.variables.set("bigName", big);// Body ilustrativo usando variável{{bigName}}// Testespm.test("Deve rejeitar name acima do limite", function () { pm.expect([400, 422]).to.include(pm.response.code); const json = pm.response.json(); pm.expect(JSON.stringify(json).toLowerCase()).to.include("name");});Paginação e limites de consulta
Em endpoints de listagem, limites comuns incluem limit máximo, offset não negativo, e combinações inválidas (ex.: page e offset ao mesmo tempo). Testes negativos aqui garantem que a API não retorna páginas gigantes por engano e não aceita parâmetros que causem consultas pesadas. Também é útil validar que, quando o cliente pede limit acima do máximo, a API responde com erro ou aplica um cap explícito e documentado (o teste deve refletir a regra do produto).
Rate limit e respostas 429
Se a API possui rate limit, um teste de robustez pode tentar disparar várias requisições em sequência para verificar se o servidor retorna 429 e, idealmente, cabeçalhos como Retry-After. Mesmo sem simular alta concorrência real, um loop controlado já detecta se o rate limit está ativo e se o cliente recebe orientação para retentativa.
// Exemplo conceitual: validar 429 e Retry-After quando ocorrerpm.test("Se retornar 429, deve informar Retry-After", function () { if (pm.response.code === 429) { pm.expect(pm.response.headers.has("Retry-After")).to.be.true; } else { pm.expect([200, 201, 204, 400, 401, 403]).to.include(pm.response.code); }});Idempotência: repetição segura e prevenção de duplicidade
Conceito prático de idempotência
Uma operação idempotente é aquela que, ao ser executada múltiplas vezes com os mesmos parâmetros, produz o mesmo efeito final no servidor. Em HTTP, GET, PUT e DELETE são frequentemente tratados como idempotentes por design, enquanto POST normalmente não é. Na prática, porém, muitos sistemas implementam idempotência também em POST usando uma chave de idempotência (por exemplo, um header Idempotency-Key) para evitar duplicidade quando o cliente reenvia a requisição por timeout ou falha de rede.
Testes de idempotência são fundamentais para robustez porque reproduzem um comportamento comum: o cliente não sabe se a requisição foi processada e tenta novamente. Sem idempotência, isso pode gerar pedidos duplicados, cobranças repetidas ou criação de recursos redundantes.
Passo a passo prático: testando idempotência em criação com Idempotency-Key
Objetivo: confirmar que duas requisições idênticas com a mesma chave não criam dois recursos.
- Passo 1: crie uma request “POST - Criar pedido (idempotente)”.
- Passo 2: adicione o header
Idempotency-Keycom um valor fixo para o teste (por exemplo,test-key-001). - Passo 3: execute a request duas vezes seguidas, sem alterar body nem headers.
- Passo 4: valide que a segunda resposta não representa uma nova criação. Dependendo da API, isso pode aparecer como: mesmo
idretornado, status200em vez de201, ou um campo indicando replay.
// Testes para comparar o id retornado entre execuçõesconst json = pm.response.json();pm.test("Deve retornar um identificador", function () { pm.expect(json).to.have.property("id");});const prevId = pm.collectionVariables.get("lastCreatedId");if (!prevId) { pm.collectionVariables.set("lastCreatedId", json.id);} else { pm.test("Repetição com mesma Idempotency-Key não deve criar novo recurso", function () { pm.expect(json.id).to.eql(prevId); });}Se a API não retorna o mesmo id na repetição, adapte o critério: você pode validar que o total de recursos não aumentou (via listagem) ou que a resposta contém um indicador de requisição repetida. O ponto é: o efeito final deve ser o mesmo, e o teste deve capturar isso de forma observável.
Idempotência em PUT e DELETE: repetição e estados finais
Para PUT, um teste robusto é enviar o mesmo payload duas vezes e confirmar que a segunda chamada não altera nada além do esperado (por exemplo, não muda updatedAt de forma indevida, se a regra do produto for manter updatedAt apenas quando há mudança real). Para DELETE, repetir a remoção do mesmo recurso deve resultar em um estado final “removido”. Algumas APIs retornam 204 sempre, outras retornam 404 na segunda tentativa. O teste deve refletir a decisão do contrato e garantir consistência.
// Exemplo: DELETE repetido aceita 204 ou 404, mas nunca 500pm.test("DELETE repetido deve ser previsível", function () { pm.expect([204, 404]).to.include(pm.response.code);});Consistência: integridade de dados e previsibilidade entre endpoints
Consistência observável pelo cliente
Consistência, do ponto de vista de testes de API, é a capacidade do sistema manter regras invariantes e apresentar resultados coerentes entre endpoints e ao longo do tempo. Exemplos práticos: um recurso criado deve aparecer na listagem (respeitando regras de eventual consistency, se existirem); um recurso deletado não deve mais ser retornado por GET; um relacionamento não deve apontar para IDs inexistentes; e campos derivados (como total de um pedido) devem bater com a soma dos itens.
Em testes negativos e de robustez, consistência aparece quando você provoca falhas e confirma que o sistema não fica “meio atualizado”. Por exemplo: enviar um payload inválido não pode criar parcialmente um recurso; uma operação que falha não deve deixar registros órfãos; e um conflito (409) não deve alterar dados existentes.
Passo a passo prático: validando que falha não gera efeito colateral
Objetivo: garantir que uma requisição inválida não cria recurso nem altera contadores/estado.
- Passo 1: antes do teste negativo, faça uma listagem e capture um indicador estável, como a contagem de itens retornados (quando aplicável) ou um conjunto de IDs.
- Passo 2: execute a requisição inválida (por exemplo, criar pedido com item sem
productId). - Passo 3: faça a listagem novamente e compare o indicador. A contagem não deve aumentar e nenhum novo ID deve aparecer.
// Exemplo conceitual: capturar contagem antes e depois// Request A (GET /orders?limit=...) - Testsconst before = pm.response.json();pm.collectionVariables.set("ordersCountBefore", before.items.length);// Request B (POST /orders inválido) - Tests: validar erro// Request C (GET /orders?limit=...) - Testsconst after = pm.response.json();const beforeCount = parseInt(pm.collectionVariables.get("ordersCountBefore"), 10);pm.test("Requisição inválida não deve criar novo pedido", function () { pm.expect(after.items.length).to.eql(beforeCount);});Esse padrão é útil, mas exige cuidado: listagens podem ser paginadas, filtradas e sujeitas a ordenação. Para reduzir flakiness, prefira comparar por IDs quando possível, ou use filtros determinísticos (por exemplo, um campo de referência do teste) para isolar apenas os recursos do cenário.
Consistência entre GET por ID e listagem
Um teste de robustez importante é verificar que a representação do recurso é consistente: o que aparece em GET /resource/{id} deve bater com o item correspondente na listagem (campos principais, status, valores). Inconsistências aqui costumam indicar caches desatualizados, projeções diferentes ou falhas de sincronização. Mesmo em sistemas com consistência eventual, você pode testar que a convergência acontece dentro de uma janela aceitável (por exemplo, com retentativas controladas e timeout).
// Exemplo simples: comparar campos principais entre listagem e detalhe// Supondo que você já tem um id salvo em variávelconst detail = pm.response.json();pm.test("Detalhe deve ter status coerente", function () { pm.expect(detail).to.have.property("status");});Robustez contra entradas maliciosas e formatos inesperados
Payload malformado e Content-Type incorreto
Além de valores inválidos, teste formatos inesperados: enviar JSON malformado, enviar Content-Type diferente do suportado, ou omitir Content-Type. A API deve responder com erro claro (frequentemente 400 ou 415 Unsupported Media Type) e não deve tentar “adivinhar” o formato de forma insegura.
// Teste: se Content-Type não suportado, esperar 415 (ou 400 conforme contrato)pm.test("Content-Type inválido deve ser rejeitado", function () { pm.expect([400, 415]).to.include(pm.response.code);});Campos extras e tolerância controlada
Algumas APIs aceitam campos desconhecidos e os ignoram; outras rejeitam. Ambas as abordagens podem ser válidas, mas precisam ser consistentes. Um teste de robustez aqui é enviar um campo extra (por exemplo, isAdmin em um cadastro de usuário) e verificar que: ou a API rejeita explicitamente, ou ignora sem alterar privilégios. Esse tipo de teste ajuda a prevenir “mass assignment” e outras classes de vulnerabilidade de autorização por payload.
Estratégia de organização: matriz de casos e reutilização de cenários
Matriz de negativos por endpoint
Para não depender de criatividade a cada endpoint, monte uma matriz de casos negativos e aplique como checklist: obrigatório ausente, tipo inválido, tamanho mínimo/máximo, enum inválido, formato inválido (e-mail/UUID/data), recurso inexistente, duplicidade, conflito, e repetição idempotente (quando aplicável). Em vez de criar dezenas de requests sem critério, priorize os casos com maior risco: campos críticos, regras de negócio e endpoints que geram efeitos financeiros ou de segurança.
Critérios de robustez que valem para toda a API
Alguns critérios podem ser padronizados e aplicados em vários endpoints: nunca retornar 500 para erro do cliente; mensagens de erro consistentes; tempos de resposta aceitáveis mesmo em erro; ausência de dados sensíveis; e previsibilidade em repetição de operações. Transforme esses critérios em testes reutilizáveis e aplique em todas as requests negativas, para que uma regressão em tratamento de erro seja detectada rapidamente.