Ir para o conteúdo
DedicatedPHP Contato

Testes de contrato para APIs PHP sem quebrar integrações

Aprenda a definir e automatizar contratos verificáveis para evoluir APIs PHP sem descobrir incompatibilidades quando a mudança já estiver em produção.

Diagrama editorial de uma API PHP validando contratos de solicitações e respostas antes da implantação

Uma API não é definida apenas por seu controlador PHP nem por uma especificação publicada. Ela também é definida pelas expectativas que outros sistemas já incorporaram: uma rota, o nome de um campo, um código HTTP, o formato de um erro ou a ordenação necessária para percorrer páginas. Os testes de contrato para APIs PHP transformam essas expectativas em verificações automatizadas antes de fazer merge de código ou implantá-lo.

O objetivo não é impedir toda evolução. É detectar se uma mudança altera um acordo observável por um consumidor e decidi-lo conscientemente: manter a compatibilidade, introduzir uma transição ou versionar a interface. Isso é especialmente importante em APIs internas com várias equipes, integrações B2B e fluxos assíncronos nos quais a falha pode surgir horas após a publicação.

O que os testes de contrato resolvem e o que não substituem

O que os testes de contrato resolvem e o que não substituem — guía visual de DedicatedPHP

Um teste de contrato verifica se provedor e consumidor estão de acordo em uma interação: dada uma solicitação válida, o provedor produz uma resposta com uma estrutura, tipos e regras acordadas. No sentido inverso, um consumidor pode declarar quais solicitações precisa, e o provedor verifica se consegue atendê-las.

Essa abordagem detecta incompatibilidades que os testes unitários costumam não perceber. Um teste unitário pode confirmar que um serializador retorna customer_id; ele não demonstra que o consumidor continua entendendo esse campo se antes esperava customerId. Um teste de integração local pode cobrir o endpoint, mas não necessariamente registra as premissas reais de cada integração.

Eles não substituem outros controles:

  • Testes unitários, para regras de domínio, validação e transformações.
  • Testes de integração, para banco de dados, filas, cache, autenticação ou serviços conectados.
  • Testes end-to-end, para jornadas críticas completas em ambientes controlados.
  • Testes de segurança e desempenho, para autorização, abuso, exposição de dados, latência e capacidade.
  • Observabilidade em produção, para detectar consumidores que ainda usam comportamentos em descontinuação.

Um contrato também não certifica que a resposta está correta para o negócio; certifica que ela preserva a forma e a semântica declaradas. Por isso, deve ser acompanhado de exemplos que expressem regras relevantes, e não apenas schemas vazios.

O que faz parte do contrato de uma API

O contrato é qualquer comportamento que um consumidor possa observar e do qual dependa. Limitá-lo ao JSON de uma resposta bem-sucedida deixa de fora as quebras mais frequentes. Para cada operação, convém acordar no mínimo os seguintes elementos.

  • Solicitação: método, rota, parâmetros de consulta, cabeçalhos, corpo, campos obrigatórios, formatos e limites.
  • Resposta: código HTTP, cabeçalhos relevantes, estrutura, tipos, campos opcionais, campos que aceitam valores nulos e formatos de data, moeda ou identificadores.
  • Erros: códigos de status, corpo do erro, código funcional estável e condições que o geram. Um texto para pessoas pode mudar; um código como validation_failed é mais adequado para automação.
  • Paginação e filtragem: significado de limit, cursor ou página, estabilidade da ordenação, representação do próximo cursor e tratamento de conjuntos vazios.
  • Autenticação e autorização: mecanismo aceito, cabeçalhos exigidos, escopos ou permissões e diferença entre credenciais inválidas, ausência de credenciais e acesso negado.
  • Eventos e webhooks: nome do evento, versão ou schema do payload, assinatura, tentativas, identificador do evento, ordenação não garantida e expectativas de idempotência.

A obrigatoriedade de uma propriedade e a aceitação de null são regras independentes. Um campo pode ser obrigatório e aceitar null, opcional e não aceitá-lo quando aparece, ou opcional e aceitá-lo se estiver presente. Da mesma forma, uma propriedade omitida, uma propriedade presente com null e uma propriedade com string vazia são estados distintos. Se o consumidor os interpreta de forma diferente, o contrato deve expressá-los e testá-los.

Pequenas mudanças que podem quebrar consumidores

Uma modificação pode parecer inofensiva do ponto de vista do provedor e ser incompatível para um cliente gerado, um validador estrito ou uma lógica de negócio. Alterar um inteiro para uma string, por exemplo de 42 para "42", quebra comparações e schemas. Tornar um campo opcional não determina, por si só, se ele aceita null: a primeira regra define se a propriedade deve estar presente, enquanto a segunda define os valores válidos quando ela é incluída.

Outras mudanças arriscadas são retornar 200 onde antes era retornado 201, substituir uma lista vazia por null, modificar a precisão de um decimal, renomear um código de erro ou alterar a ordenação da paginação sem aviso. Adicionar um campo costuma ser compatível para leitores tolerantes, mas não é se um consumidor valida um schema fechado ou calcula assinaturas sobre o corpo completo.

A compatibilidade depende do acordo real, não de uma regra isolada. Convém classificar cada mudança de acordo com os consumidores conhecidos, a tolerância declarada e a criticidade do fluxo. Se essa informação não for conhecida, ela deve ser tratada como um risco, e não como uma suposição favorável.

Escolher entre especificação, contratos de consumidor ou ambos

Uma especificação de interface, por exemplo uma descrição OpenAPI, funciona bem como fonte comum para rotas, operações, parâmetros, schemas e respostas. Ela pode ser validada no pipeline para detectar mudanças incompatíveis em relação a uma versão de referência. É útil quando há muitos consumidores ou quando clientes e documentação são gerados a partir da mesma definição.

No entanto, um schema nem sempre captura o que importa para cada consumidor: combinações de filtros, um erro específico diante de uma condição de negócio ou uma dependência em relação a um valor de exemplo. Os contratos orientados ao consumidor declaram interações concretas de que cada consumidor precisa. O provedor os verifica contra sua implementação.

Usar ambos os níveis costuma ser razoável: a especificação governa a superfície geral, e os contratos de consumidor cobrem fluxos de alto valor ou semântica difícil de reduzir a um schema. Deve haver uma responsabilidade clara sobre cada artefato. Se uma especificação não for atualizada quando o código mudar, ela deixa de ser uma fonte de verdade e passa a ser documentação fictícia.

Exemplos representativos e casos-limite

Um exemplo de contrato deve conter dados realistas em estrutura, não dados de produção. Para um recurso de pedido, inclua um caso com itens, um vazio se for válido, identificadores no formato acordado e datas completas com fuso horário quando essa for a convenção. Adicione casos de autorização negada, validação com falha, recurso inexistente e paginação final.

Evite fixar detalhes irrelevantes que mudam legitimamente, como um identificador aleatório, a hora atual ou a ordenação das propriedades JSON. Use asserções precisas sobre o que é estável e tolerância explícita sobre o que é variável. Cada exemplo deve responder a uma necessidade conhecida; uma coleção enorme de respostas inventadas aumenta a manutenção sem elevar a confiança.

Implementação progressiva em uma API PHP existente

Não é necessário modelar toda a API antes de obter valor. Comece com um inventário de consumidores: aplicações internas, clientes B2B, processos batch, aplicações móveis, automações e receptores de webhooks. Registre responsável, canal de contato, operação utilizada, criticidade e capacidade de atualização.

Depois, priorize endpoints que criam ou modificam recursos, autenticam usuários, alimentam processos financeiros ou acionam automações. Estabeleça uma linha de base de seu comportamento atual por meio de uma especificação revisada e testes contra uma instância reproduzível da API. Em PHP, o teste deve exercitar a camada HTTP real da aplicação, e não invocar diretamente uma classe de serviço: o contrato inclui routing, middleware, serialização e tratamento de exceções.

POST /api/orders
Authorization: Bearer token
Content-Type: application/json

{"items":[{"sku":"ABC-1","quantity":2}]}

201 Created
{"id":"ord_123","status":"pending","items":[...]}

O exemplo anterior só é útil se vier acompanhado de regras: quais campos são obrigatórios, se id é sempre uma string, quais erros um SKU inválido retorna e se o estado inicial é garantido. São essas regras que devem ser transformadas em asserções.

Validação na integração contínua e antes da implantação

O pipeline deve falhar antes do merge se a implementação não cumprir contratos aprovados. Um fluxo prático inclui executar testes unitários, subir dependências controladas, iniciar a API PHP com configuração de teste e validar especificação, contratos de provedor e contratos de consumidores representativos. Os testes devem usar dados isolados e determinísticos para que uma falha seja reproduzível.

Em uma solicitação de mudança, compare também a especificação proposta com a versão publicada para apontar remoções de rotas, endurecimento de requisitos de presença, mudanças na aceitação de null, mudanças de tipo e respostas removidas. O diagnóstico deve indicar operação, interação e regra não cumprida; um simples erro de schema obriga a investigar demais.

Antes da implantação, execute a mesma suíte no artefato que será publicado, e não em uma compilação diferente. Após implantar, monitore códigos de erro, falhas de desserialização relatadas por clientes, uso de versões e tráfego para rotas descontinuadas. A validação prévia reduz o risco; ela não substitui a confirmação do comportamento sob tráfego real.

Compatibilidade, depreciação e remoção segura

Compatibilidade, depreciação e remoção segura — guía visual de DedicatedPHP

Quando uma mudança não for compatível, prefira uma transição explícita. Você pode adicionar um novo campo enquanto preserva o anterior, introduzir uma nova operação ou versão e comunicar uma data de remoção respaldada por sinais de uso. A depreciação é um período operacional com responsáveis, comunicação e medição; não é apenas uma nota na documentação.

Não remova um comportamento porque um prazo passou se você não consegue identificar consumidores pendentes ou se o fluxo é crítico. Quando possível, exponha avisos controlados e métricas para localizar uso antigo sem alterar a resposta. A exposição gradual de uma nova versão permite observar erros e corrigir contratos antes de estender a mudança.

Os erros mais comuns são testar apenas respostas bem-sucedidas, modelar mensagens humanas em vez de códigos de erro, presumir que todos os clientes ignoram campos novos e não envolver consumidores reais ou representativos. Os testes de contrato agregam valor quando refletem acordos mantidos por ambas as partes e são executados como uma condição normal de entrega.

Deseja aplicar essas ideias ao seu projeto?Vamos discutir sua plataforma PHP.
Veja os serviços relacionados