Idempotência, concorrência e consistência em testes de API

Capítulo 10

Tempo estimado de leitura: 10 minutos

+ Exercício

Idempotência aplicada em testes de API

Idempotência é a propriedade de uma operação produzir o mesmo resultado observável quando executada uma ou mais vezes com os mesmos parâmetros e no mesmo estado inicial. Em testes de API, isso significa verificar não só o status e o body, mas também os efeitos colaterais: criação de registros, alterações de saldo, envio de eventos, duplicidade de itens, e mudanças em campos como updatedAt ou version.

O que validar como “mesmo resultado observável”

  • Estado do recurso: o recurso final deve ser igual após 1 ou N chamadas.
  • Ausência de duplicidade: não pode haver criação duplicada, cobranças repetidas, múltiplos eventos idênticos etc.
  • Resposta consistente: a API pode responder com códigos diferentes em repetições (ex.: 200 vs 204), mas deve manter a semântica e o estado final.
  • Metadados: alguns metadados podem mudar (ex.: updatedAt) dependendo da implementação; o teste deve definir o que é aceitável e o que é sinal de efeito duplicado.

Testando PUT: repetição sem efeitos adicionais

Em geral, PUT é usado para substituir/atualizar um recurso em um identificador conhecido. A repetição do mesmo PUT deve manter o recurso no mesmo estado final.

Passo a passo prático

  1. Prepare um recurso existente (ou crie um e capture o id).
  2. Envie um PUT com um payload completo e determinístico.
  3. Leia o recurso (GET) e armazene o estado relevante (campos de negócio).
  4. Repita o mesmo PUT (mesmo payload).
  5. Leia novamente e compare os campos de negócio com o estado anterior.
  6. Verifique efeitos colaterais: contadores, logs de auditoria, eventos, ou qualquer endpoint auxiliar que evidencie duplicidade.

Exemplo de requisição

PUT /users/123 HTTP/1.1 Content-Type: application/json { "name": "Ana", "email": "ana@example.com", "status": "active" }

Critérios de verificação

  • Após a 1ª e 2ª chamada, GET /users/123 retorna os mesmos valores de negócio (name, email, status).
  • Não há criação de recursos adicionais relacionados (ex.: não cria “novo usuário”).
  • Se existir version ou updatedAt, defina regra:
    • Estrito: não deve mudar em repetição idêntica (útil quando a API implementa detecção de no-op).
    • Flexível: pode mudar, mas não pode gerar efeitos de negócio (ex.: não dispara novo evento de “status changed” se nada mudou).

Testando DELETE: repetição e “already deleted”

DELETE tende a ser idempotente: apagar algo duas vezes deve resultar em “não existe” sem causar novos efeitos. A resposta pode variar por implementação (ex.: 204 na primeira e 404 na segunda), mas o estado final deve ser consistente.

Passo a passo prático

  1. Garanta que o recurso existe.
  2. Envie DELETE.
  3. Valide que o recurso não está mais acessível (GET retorna “não encontrado” ou equivalente).
  4. Repita o DELETE.
  5. Valide que o estado permanece “não existe” e que não houve efeitos adicionais.

Critérios de verificação

  • Após a primeira exclusão, o recurso não pode ser retornado como ativo.
  • A segunda exclusão não pode “deletar outra coisa” nem alterar contadores indevidamente.
  • Se houver deleção lógica (soft delete), valide que o marcador de deleção não alterna de forma incorreta.

POST idempotente com Idempotency-Key

POST normalmente não é idempotente, pois cria novos recursos. Em operações críticas (pagamentos, criação de pedidos, emissão de faturas), muitas APIs oferecem idempotência via header Idempotency-Key. A ideia: o cliente envia uma chave única por intenção de operação; se repetir a requisição (por timeout, retry, replay), o servidor retorna o mesmo resultado da primeira execução, sem duplicar efeitos.

Como a Idempotency-Key deve se comportar

  • Mesma chave + mesmo endpoint + mesmo payload → mesma resposta (ou resposta equivalente) e nenhum efeito duplicado.
  • Mesma chave + payload diferente → erro de conflito/validação (comum: 409 ou 422) para evitar ambiguidade.
  • Janela de retenção: a chave costuma expirar (minutos/horas). Testes devem considerar o TTL documentado.

Passo a passo prático: POST com retry por timeout

  1. Gere uma chave única (ex.: UUID) e fixe-a para o teste.
  2. Envie POST com Idempotency-Key.
  3. Simule um cenário de incerteza do cliente:
    • timeout no cliente (não recebeu resposta), ou
    • falha de rede após enviar a requisição.
  4. Reenvie o mesmo POST com a mesma Idempotency-Key.
  5. Valide que não houve duplicidade: o id do recurso criado é o mesmo, ou o servidor retorna referência ao mesmo resultado.

Exemplo

POST /payments HTTP/1.1 Content-Type: application/json Idempotency-Key: 7f3b2c2e-1a2b-4c1f-9f8a-2d9b9b3f9a10 { "amount": 1000, "currency": "BRL", "customerId": "c-10" }

Critérios de verificação para detectar efeitos duplicados

  • Identificador único: a segunda resposta deve apontar para o mesmo paymentId (ou mesmo orderId).
  • Listagem/consulta: ao buscar pagamentos do cliente, deve existir apenas um registro correspondente àquela intenção.
  • Campos de auditoria: não deve haver duas entradas de “captura”/“cobrança” para a mesma chave.
  • Eventos: se houver endpoint/stream de eventos, não deve haver duplicação do evento de criação.

Repetição, replays, timeouts e retries: como testar com segurança

Retry vs replay

  • Retry: reenvio automático após falha transitória (timeout, 502/503, conexão reset).
  • Replay: reenvio posterior (às vezes manual) de uma requisição já executada, podendo ocorrer fora da janela imediata.

Matriz prática de cenários de repetição

CenárioO que aconteceO que o teste deve verificar
Timeout no cliente, servidor processouCliente não sabe se criou/atualizouReenvio não duplica; retorna mesmo resultado (Idempotency-Key) ou mantém estado (PUT/DELETE)
Timeout no cliente, servidor não processouNenhum efeito ocorreuReenvio executa uma vez; estado final correto
Retry após 503Falha transitóriaOperação completa sem duplicidade; logs/contadores consistentes
Replay fora do TTL da Idempotency-KeyServidor pode não reconhecer a chaveComportamento conforme contrato: pode criar novo recurso; teste deve validar a regra documentada
Mesmo Idempotency-Key com payload diferenteAmbiguidadeErro de conflito/validação e nenhum efeito adicional

Como simular timeouts e falhas

  • Timeout do cliente: configure o client de teste com timeout baixo e chame um endpoint que pode demorar (ou use um parâmetro de atraso se existir em ambiente de teste).
  • Falha após envio: em testes automatizados, é comum simular via proxy/fault injection (reset de conexão) ou interromper a leitura da resposta.
  • Retries controlados: implemente retry no teste com política explícita (número máximo, backoff) para reproduzir comportamento real do consumidor.

Checks para detectar duplicidade (além do status/body)

  • Invariantes numéricos: saldo, estoque, contagem de itens do carrinho, total do pedido.
  • Unicidade: campos únicos (ex.: externalReference, idempotencyKey armazenada) não podem aparecer duplicados.
  • Consultas por janela: buscar registros criados “nos últimos X minutos” e filtrar por atributos do payload.
  • Idempotency-Key ecoada: algumas APIs retornam a chave ou um identificador de requisição; use isso para correlacionar.

Concorrência em testes de API: atualizações simultâneas e conflitos

Concorrência aparece quando dois clientes (ou duas threads) tentam modificar o mesmo recurso ao mesmo tempo. Sem controle, ocorre “lost update” (uma atualização sobrescreve a outra). Testes de concorrência devem validar que a API detecta conflitos ou fornece mecanismos para evitá-los.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

Cenário 1: duas atualizações simultâneas sem controle otimista

Objetivo do teste: identificar se a API sofre com lost update.

  1. Leia o recurso e obtenha o estado inicial.
  2. Dispare duas requisições de atualização em paralelo, cada uma alterando um campo diferente.
  3. Leia o recurso ao final.

Critérios de verificação:

  • Se a API não oferece controle de versão, o resultado pode ser “última escrita vence”. O teste deve registrar esse comportamento e avaliar se é aceitável para o domínio.
  • Se o domínio exige consistência forte (ex.: saldo), “última escrita vence” costuma ser bug.

Cenário 2: controle otimista com ETag e If-Match

Controle otimista evita sobrescritas silenciosas. O servidor fornece uma versão do recurso (frequentemente via ETag). O cliente envia If-Match com a ETag que ele leu; se o recurso mudou, o servidor rejeita com conflito (comum: 412 Precondition Failed ou 409 Conflict, dependendo do contrato).

Passo a passo prático

  1. GET no recurso e capture a ETag da resposta.
  2. Cliente A: envia atualização com If-Match: <etag> e payload.
  3. Cliente B: usando a mesma ETag antiga, envia outra atualização concorrente.
  4. Valide que uma das operações falha por conflito/precondição.
  5. Faça novo GET e confirme que o estado reflete apenas a atualização vencedora.

Exemplo

GET /profiles/55 HTTP/1.1 200 OK ETag: "v3" { "id": 55, "nickname": "ana", "bio": "..." } PUT /profiles/55 HTTP/1.1 Content-Type: application/json If-Match: "v3" { "bio": "bio atualizada" }

Critérios de verificação:

  • Uma atualização deve retornar sucesso e a outra deve retornar 412 ou 409 (conforme contrato).
  • A atualização rejeitada não pode ter aplicado efeitos parciais.
  • Após re-GET, a ETag deve ter mudado (ex.: v4), indicando nova versão.

Como estruturar testes concorrentes

  • Barreira de sincronização: preparar duas threads/processos para disparar requests no mesmo instante.
  • Repetição: rodar o cenário várias vezes para aumentar chance de colisão.
  • Isolamento: usar recursos dedicados por execução (ids únicos) para evitar interferência entre testes.
  • Observabilidade: registrar timestamps e IDs de correlação para entender a ordem real.

Consistência eventual: como testar sem falsos negativos

Em arquiteturas distribuídas, a escrita pode ser aceita e propagada de forma assíncrona. Isso gera uma janela em que leituras (ou sistemas derivados como busca, relatórios, caches) ainda não refletem a atualização. Testes precisam distinguir “ainda não propagou” de “nunca vai propagar”.

Padrão de teste: assertiva com polling e timeout

  1. Execute a operação de escrita (criar/atualizar).
  2. Defina um timeout total (ex.: 10–60s, conforme SLO do sistema) e um intervalo (ex.: 200–1000ms).
  3. Faça polling em um endpoint de leitura/consulta até a condição ser verdadeira ou o timeout estourar.
  4. Se estourar, falha com evidência: últimas respostas, tempo decorrido e correlação.

Critérios de verificação em consistência eventual

  • Condição final: o estado esperado deve aparecer dentro do timeout.
  • Monotonicidade (quando aplicável): após aparecer, não deve “regredir” para estado anterior.
  • Leituras diferentes: se houver múltiplas fontes (ex.: endpoint principal e endpoint de busca), valide cada uma com janelas/SLAs distintos.

Cenários comuns

  • Criação e indexação: cria um item e ele só aparece na busca após alguns segundos.
  • Atualização e cache: GET imediato retorna valor antigo por cache; após expiração/invalidação, retorna o novo.
  • Eventos assíncronos: a escrita dispara processamento posterior (ex.: cálculo, enriquecimento). O teste deve aguardar o campo derivado aparecer.

Checklist de cenários e critérios de verificação

Idempotência

  • PUT repetido: estado final igual; sem efeitos duplicados.
  • DELETE repetido: estado final “não existe”; sem efeitos adicionais.
  • POST com Idempotency-Key: reenvio retorna mesmo resultado; mesma chave com payload diferente gera erro; sem duplicidade em consultas.

Retries e timeouts

  • Timeout com incerteza: reenvio não duplica; invariantes preservadas.
  • Retry em falha transitória: operação completa uma vez; sem múltiplos registros/eventos.

Concorrência

  • Atualizações simultâneas: detectar lost update ou validar estratégia de resolução.
  • ETag/If-Match: conflito/precondição em versão antiga; nenhuma aplicação parcial.

Consistência eventual

  • Polling com timeout: condição final aparece dentro da janela.
  • Sem regressão: após convergir, leituras não voltam ao estado anterior.

Agora responda o exercício sobre o conteúdo:

Ao testar um POST com Idempotency-Key após um timeout no cliente, qual comportamento indica que a API está garantindo idempotência corretamente?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Com a mesma Idempotency-Key, repetir o POST após incerteza (ex.: timeout) deve devolver um resultado equivalente ao original e não duplicar registros, cobranças ou eventos.

Próximo capitúlo

Testes de segurança básica em APIs: validações indispensáveis

Arrow Right Icon
Capa do Ebook gratuito Testes de API: Conceitos Essenciais (REST, HTTP, Status Codes e Contratos)
67%

Testes de API: Conceitos Essenciais (REST, HTTP, Status Codes e Contratos)

Novo curso

15 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.