Contratos de API em testes (OpenAPI/Swagger): estrutura e validações

Capítulo 8

Tempo estimado de leitura: 9 minutos

+ Exercício

O que é um contrato de API e por que ele guia os testes

Um contrato de API é uma especificação formal do que a API aceita e do que ela devolve: endpoints, formatos de payload, tipos, campos obrigatórios, regras de validação e exemplos. Em testes, o contrato funciona como uma fonte de verdade verificável: você deriva casos de teste a partir dele e valida se o comportamento real está aderente ao que foi prometido.

Quando o contrato está em OpenAPI (Swagger), ele pode ser lido por humanos e também por ferramentas para validação automática, geração de mocks, geração de clientes e checagens de compatibilidade.

  • Testes guiados por contrato: o contrato define o que testar (campos, tipos, regras, cenários de erro).
  • Detecção de divergências: documentação diz uma coisa, implementação faz outra (ou vice-versa).
  • Prevenção de breaking changes: mudanças que quebram consumidores podem ser detectadas comparando versões do contrato.

Como ler um documento OpenAPI: mapa mental da estrutura

Um documento OpenAPI (YAML/JSON) costuma ter estas seções principais:

  • openapi: versão da especificação (ex.: 3.0.3, 3.1.0).
  • info: metadados (nome, versão do serviço).
  • servers: URLs base.
  • paths: endpoints e operações (GET/POST/PUT...).
  • components: reutilização (schemas, parameters, responses, securitySchemes).

Paths e operations

Em paths, cada rota (ex.: /users/{id}) contém operações (ex.: get, put). Em cada operação, você encontra:

  • operationId: identificador único (útil para rastrear testes).
  • parameters: parâmetros de path/query/header/cookie.
  • requestBody: corpo da requisição (quando aplicável).
  • responses: respostas por status code, com conteúdo e schema.
  • tags, summary, description: documentação (não executável, mas útil para intenção).

Exemplo mínimo (trecho OpenAPI)

paths:
  /users/{id}:
    get:
      operationId: getUser
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
              examples:
                ok:
                  value:
                    id: 10
                    name: "Ana"
                    status: "ACTIVE"

Parameters: como interpretar e transformar em validações

Cada item em parameters tem:

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

  • name e in (path/query/header/cookie).
  • required: obrigatório ou não.
  • schema: tipo e restrições.
  • example ou examples: valores ilustrativos (podem virar casos de teste).

Restrições comuns em parameters

  • Numéricas: minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf.
  • Strings: minLength, maxLength, pattern (regex), format (ex.: email, uuid, date-time).
  • Enum: enum: [A, B, C].
  • Arrays: items, minItems, maxItems, uniqueItems.

Em testes, cada restrição vira pelo menos: um caso válido e um caso inválido (limites e formatos).

requestBody: conteúdo, schema e obrigatoriedade

requestBody descreve o corpo da requisição e normalmente inclui:

  • required: se o corpo é obrigatório.
  • content: mapa por media type (ex.: application/json), contendo schema e exemplos.

Exemplo com required, enum e constraints

requestBody:
  required: true
  content:
    application/json:
      schema:
        type: object
        required: [name, status]
        properties:
          name:
            type: string
            minLength: 2
            maxLength: 60
          status:
            type: string
            enum: [ACTIVE, INACTIVE]
          email:
            type: string
            format: email
      examples:
        create:
          value:
            name: "Ana"
            status: "ACTIVE"
            email: "ana@exemplo.com"

Note a diferença entre:

  • requestBody.required: exige a presença do corpo.
  • schema.required: exige campos dentro do JSON.

responses: o que validar em cada status code

Em responses, cada status code define:

  • description: intenção.
  • headers (opcional): headers específicos da resposta.
  • content: por media type, com schema e exemplos.

Exemplo com múltiplas respostas

responses:
  '200':
    description: OK
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/User'
  '404':
    description: Not Found
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/Error'

Em testes de contrato, você valida que:

  • Para um cenário de sucesso, o corpo atende ao schema do 200.
  • Para um cenário de recurso inexistente, o corpo atende ao schema do 404.
  • O tipo de cada campo e as regras (required, enum, min/max, pattern) são respeitados.

Schemas (components/schemas): tipos, required e composição

Os schemas definem a estrutura dos objetos. Em OpenAPI 3, você verá frequentemente:

  • type: object com properties e required.
  • type: array com items.
  • $ref para reutilizar schemas.
  • Composição: oneOf, anyOf, allOf (atenção: isso impacta muito os testes).

Exemplo de schema com constraints e required

components:
  schemas:
    User:
      type: object
      required: [id, name, status]
      properties:
        id:
          type: integer
          minimum: 1
        name:
          type: string
          minLength: 2
        status:
          type: string
          enum: [ACTIVE, INACTIVE]
        tags:
          type: array
          items:
            type: string
          maxItems: 10
          uniqueItems: true

Exemplos (example/examples): como usar sem cair em armadilhas

example e examples ajudam a entender o payload esperado e podem ser usados para:

  • Gerar rapidamente um caso “happy path”.
  • Servir de base para mutações (alterar um campo por vez para criar casos inválidos).

Cuidados comuns:

  • Exemplo pode estar desatualizado e não refletir o schema (ex.: campo faltando, enum inválido).
  • Exemplo pode omitir campos opcionais importantes para cenários reais.

Enums e constraints: como derivar casos de teste sistematicamente

Enum

Para um campo com enum, derive:

  • Um teste para cada valor permitido (ou amostragem representativa, se a lista for grande).
  • Um teste com valor fora do enum (ex.: "BLOCKED" quando só aceita ACTIVE/INACTIVE).
  • Um teste com tipo errado (ex.: número em vez de string), se o schema permitir detectar.

Min/Max e limites

Para minimum: 1 e maximum: 10:

  • Válidos: 1, 10 e um valor intermediário (ex.: 5).
  • Inválidos: 0 e 11.
  • Se for exclusiveMinimum, ajuste (ex.: mínimo exclusivo 1 → 1 é inválido, 2 é válido).

Pattern (regex)

Para pattern, crie:

  • Um valor que casa com a regex (válido).
  • Um valor que falha por pouco (inválido), para garantir que a validação não é “solta”.
code:
  type: string
  pattern: '^[A-Z]{3}-\d{4}$'

# Válido: "ABC-1234"
# Inválido: "abc-1234" (case), "AB-1234" (tamanho), "ABC-123" (dígitos)

Required

Para required em objetos:

  • Teste válido com todos os campos obrigatórios presentes.
  • Teste inválido removendo um campo obrigatório por vez (para identificar mensagens e comportamento).
  • Teste com campos extras (se a API aceita ou rejeita; em JSON Schema isso pode ser controlado por additionalProperties).

Derivando casos de teste do contrato (passo a passo prático)

Passo 1 — Escolha uma operação e liste entradas e saídas

Para uma operação (ex.: POST /users):

  • Entradas: parameters + requestBody (campos, tipos, required, constraints).
  • Saídas: responses por status code (schema e constraints).

Passo 2 — Monte uma matriz de casos a partir das regras

Crie uma tabela de cobertura mínima por campo/regra:

ElementoRegra no contratoTeste válidoTeste inválido
requestBodyrequired: trueEnviar bodySem body
nameminLength 2"Ana""A"
statusenum"ACTIVE""BLOCKED"
emailformat email"a@b.com""abc"

Passo 3 — Gere variações por mutação controlada

Parta de um payload válido (geralmente do example) e altere um aspecto por vez:

  • Remover campo obrigatório.
  • Trocar tipo (string → number).
  • Quebrar pattern.
  • Estourar maxLength/maxItems.
  • Enviar enum inválido.

Isso ajuda a isolar a causa quando o comportamento diverge do contrato.

Passo 4 — Valide compatibilidade de tipos e obrigatoriedade na resposta

Para cada resposta esperada:

  • Valide que o corpo é compatível com o schema (tipos corretos, campos required presentes).
  • Valide constraints (ex.: id >= 1, status dentro do enum).
  • Valide arrays e objetos aninhados (itens e required internos).

Passo 5 — Cubra cenários alternativos definidos por oneOf/anyOf

Se o schema usar oneOf (ex.: resposta pode ser um de dois formatos), derive testes para cada alternativa e garanta que a resposta se encaixa em apenas uma (quando aplicável).

Identificando divergências entre documentação e comportamento real

Durante a execução, divergências típicas aparecem como:

  • Campo documentado como required, mas ausente na resposta real.
  • Tipo diferente (ex.: contrato diz integer, API retorna string "10").
  • Enum “expandido” na prática (API retorna valor não documentado).
  • Constraints não aplicadas (API aceita name com 1 caractere apesar de minLength: 2).
  • Schema desatualizado (campo novo aparece sem estar no contrato).
  • Exemplos inconsistentes com o schema.

Como tratar:

  • Se a implementação está correta e o contrato está errado: atualizar o OpenAPI e versionar.
  • Se o contrato está correto e a implementação está errada: abrir bug e priorizar correção (pode ser breaking change para consumidores).
  • Se há ambiguidade: ajustar o contrato para remover interpretações (ex.: definir nullable, oneOf, required, exemplos).

Roteiro de validação automática de contrato

1) Validação estática do documento OpenAPI

  • Rodar um validador/linter de OpenAPI para checar estrutura, referências quebradas ($ref) e inconsistências.
  • Checar se exemplos respeitam os schemas (quando a ferramenta suportar).

2) Validação de requisição (client-side) baseada no contrato

  • Antes de enviar, validar se o payload gerado pelos testes atende ao schema (útil para garantir que seus testes não estão enviando lixo sem querer).
  • Gerar dados válidos automaticamente a partir do schema (quando possível) e aplicar mutações para negativos.

3) Validação de resposta (server-side behavior) baseada no contrato

  • Após cada chamada, validar o corpo retornado contra o schema do status code correspondente.
  • Validar headers documentados (quando existirem no contrato).
  • Registrar divergências com evidência: operação, status code, caminho do campo (ex.: $.status), valor recebido e regra violada.

4) Checagem de compatibilidade entre versões do contrato

  • Comparar contrato atual vs anterior para detectar mudanças potencialmente quebradoras: remoção de campos, mudança de tipo, tornar campo obrigatório, remover valores de enum, alterar constraints para mais restritivas.
  • Bloquear pipeline quando houver breaking change não aprovado.

5) Execução contínua em ambientes

  • Executar validação de contrato em CI e também periodicamente em ambiente de homologação/staging para detectar drift (implementação mudou sem atualizar contrato).

Roteiro de validação manual (checklist prático)

Leitura dirigida do OpenAPI

  • Escolha 1–2 operações críticas e leia: parametersrequestBodyresponses.
  • Liste campos required e constraints relevantes (min/max, pattern, enum).
  • Verifique se há exemplos e se parecem realistas.

Execução manual com foco em aderência ao contrato

  • Faça uma chamada com payload do exemplo e compare a resposta com o schema (campos, tipos, enum).
  • Remova um required por vez e observe se a API rejeita como esperado.
  • Teste limites (min/max) e padrões (pattern) com valores “quase válidos”.
  • Verifique se a API retorna campos não documentados e se isso é aceitável para consumidores.

Registro de divergências

  • Para cada divergência, anote: operação (operationId), request enviada, response recebida, trecho do contrato e impacto (breaking ou não).
  • Classifique: erro de documentação vs erro de implementação vs ambiguidade do contrato.

Dicas de organização dos testes orientados a contrato

  • Nomeie testes por operationId + regra (ex.: createUser_required_name).
  • Mantenha um conjunto de payloads válidos “base” por operação e aplique mutações pequenas para negativos.
  • Priorize validações que mais quebram integrações: required, tipos, enums e formatos.
  • Quando houver $ref compartilhado, crie testes que exercitem o schema em diferentes operações para detectar inconsistências sistêmicas.

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

Em testes orientados a contrato com OpenAPI, qual abordagem ajuda a criar casos negativos de forma sistemática e a isolar a causa quando houver divergência do comportamento da API?

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

Você errou! Tente novamente.

A estratégia recomendada é usar um payload válido como base e aplicar mutações pequenas, uma por vez, criando casos inválidos (required, tipo, enum, limites). Isso facilita derivar testes do contrato e identificar exatamente qual regra foi violada.

Próximo capitúlo

Validação de dados e regras de negócio em testes de API: além do contrato

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

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.