Por que a automação de API trava sempre nos mesmos quatro pontos
Todo time começa da mesma forma. Alguém escreve uma requisição para um único endpoint, faz uma asserção sobre o código de status e coloca isso na CI. Funciona. Aí a suíte cresce, e quatro problemas aparecem praticamente na mesma ordem para todo mundo.
Tokens expiram, então a suíte passa de manhã e falha na hora do almoço. As respostas trazem valores de que a próxima requisição precisa, então os testes ou fixam esses valores no código ou quebram. Alguns endpoints só podem ser chamados depois que outra coisa foi criada, então a ordem passa a importar e ninguém anotou essa ordem em lugar nenhum. E cada execução deixa registros para trás, então a décima execução se comporta de um jeito diferente da primeira.
Nada disso é exótico. Esse é o formato comum de um backend real. A diferença entre uma suíte que dura e uma que é apagada depois de um trimestre está em ter projetado a suíte para esses quatro pontos ou ter descoberto cada um deles no caminho.
Comece pelo que o serviço realmente expõe
Antes de tudo isso, você precisa de um inventário. Escrever testes de memória produz uma suíte que cobre os endpoints de que você por acaso lembrou, que costumam ser justamente os que já estavam funcionando.
Duas entradas te dão um panorama real. Um documento de especificação, como um arquivo OpenAPI ou um documento de requisitos de produto, diz o que deveria existir. Uma passada de descoberta sobre o serviço em execução diz o que de fato existe. A diferença entre os dois já é interessante por si só, e é um ponto de partida melhor para decisões de cobertura do que uma página em branco. No TestSprite, esse primeiro passo é o API Discovery, e ele aceita um PRD, documentação de API ou a observação de chamadas reais.
A partir daí você escolhe os endpoints que valem a pena cobrir, em vez de aceitar tudo. Um plano gerado que inclui todas as rotas administrativas e internas é ruído, e é assim que uma suíte perde seu público.
Problema um: autenticação (Auto-Authentication)
A abordagem ingênua coloca um token numa variável de ambiente e o atualiza na mão quando alguém percebe as falhas. Isso funciona até o dia em que a pessoa que sabe disso está de férias.
O que você quer, em vez disso, é que as credenciais pertençam ao ambiente e não a nenhum teste individual, e que o login seja feito como parte da execução. O TestSprite chama isso de Auto-Authentication: você configura o fluxo de login uma vez e um token novo é obtido antes de cada execução. Dois princípios importam mais do que parecem.
Separe as credenciais do teste. Um teste que contém uma senha não pode ser compartilhado, não pode ser apontado para um segundo ambiente e não pode ir para o repositório. O caso deve dizer "como um usuário autenticado", e não "como user@example.com com esta senha".
Autentique uma vez por execução, onde o serviço permitir. Qualquer serviço com limite de tentativas de login vai começar a rejeitar uma suíte que faz login em cada caso, e essas rejeições parecem exatamente bugs do produto. Diagnosticar esse tipo de falha falsa custa mais tempo do que projetar a suíte para evitá-la.
Problema dois: valores que só existem em tempo de execução (Dynamic Variables)
Uma chamada de criação retorna um identificador. As chamadas de atualização e de exclusão precisam dele. Se o identificador for colado dentro do teste, o teste é válido exatamente uma vez.
A solução é capturar o valor da resposta e referenciá-lo depois, para que a suíte leve os dados adiante em vez de presumi-los. Na prática, isso significa que um teste não diz DELETE /orders/8412. Ele diz para excluir o pedido que esta execução criou, qualquer que tenha sido o identificador.
Escrito assim parece óbvio, e é a razão isolada mais comum para uma suíte que passava no mês passado falhar hoje. No TestSprite o mecanismo tem nome, Dynamic Variables: capturar um valor de uma resposta e injetá-lo na requisição seguinte, com as ligações derivadas em vez de mantidas na mão.
Problema três: endpoints que dependem uns dos outros (Dependency Chains)
Você não consegue adicionar um item a um carrinho que não existe. Você não consegue finalizar a compra de um carrinho sem itens. Cobertura real de API não é uma lista plana de chamadas, é um grafo.
Quando esse grafo é implícito, duas coisas dão errado. Os testes rodam numa ordem que por acaso funciona em uma máquina e não em outra, e uma única falha logo no início produz vinte falhas seguintes que parecem todas bugs distintos.
Tornar as dependências explícitas resolve as duas coisas. Cada caso declara o que precisa e o que produz, a execução deriva daí uma ordem segura e, quando algo falha, você recebe uma falha com dezenove casos ignorados em vez de vinte linhas vermelhas para ler. Essa é a diferença entre os Endpoint Tests, que cobrem uma rota por vez, e os Integration Tests, que são cadeias de vários passos em que uma resposta alimenta a próxima.
Problema quatro: os dados que você deixa para trás (Auto-Cleanup)
Uma suíte que cria registros e nunca os remove se degrada no próprio ritmo. As listas ficam mais longas, as asserções de paginação saem do lugar, restrições de unicidade começam a disparar e, em algum momento, alguém trunca uma tabela na mão numa sexta-feira.
A limpeza precisa fazer parte da execução, ligada aos recursos que aquela execução específica criou. Limpar por padrão de nome, como apagar tudo que começa com test-, funciona até duas execuções se sobreporem ou até alguém da empresa criar um registro com um nome azarado. O Auto-Cleanup remove os registros que uma execução criou quando essa execução termina, que é a versão que sobrevive à concorrência.
Como é uma automação de API que funciona de ponta a ponta
Juntando os quatro pontos, uma configuração que se sustenta ao longo de um ano fica assim.
| Uma vez, no começo | Dê ao projeto a sua especificação e deixe uma passada de descoberta enumerar os endpoints ativos. Selecione os endpoints que valem a pena cobrir. Resista à cobertura total no primeiro dia. Configure credenciais por ambiente, não por teste. |
| Por caso | Capture os valores de que o passo seguinte precisa em vez de fixá-los no código. Declare o que o caso exige e o que ele produz. Faça asserções sobre o comportamento, não só sobre códigos de status. Um 200 que retorna o corpo errado continua sendo um bug. |
| Por execução | Autentique uma vez e reutilize a sessão. Deixe o grafo de dependências decidir a ordem de execução. Ao terminar, remova exatamente o que aquela execução criou. |
Colocando isso no pipeline
Uma suíte de API que roda quando alguém lembra é uma suíte que roda antes dos releases e não antes dos merges, que é a ponta errada. Há dois caminhos de entrada, e a escolha é sobre quem você quer que seja dono do gatilho.
Vale saber antes de escolher: são duas superfícies e elas resolvem isso de formas diferentes. O portal web é o caminho de gerar e manter, em que a descoberta, as ligações de dependência e a limpeza são derivadas para você. A CLI é o caminho de trazer o próprio código: ali os testes de backend são Python escrito por você, é você mesmo quem declara o que cada um precisa e produz, e o teardown é um teste que você marca como tal. Os mesmos quatro problemas, uma divisão de trabalho diferente. Times que querem a suíte morando no próprio repositório escolhem a segunda opção de propósito.
Dois caminhos, e a escolha é sobre onde você quer que a configuração fique.
Dois caminhos, e a escolha é, no fundo, sobre quem é dono do pipeline. Conectar o GitHub App pelo dashboard não exige nenhuma mudança no seu repositório, porque ele reage ao deploy que o seu build já emite. Adicionar um passo de GitHub Actions coloca a verificação no repositório, onde ela passa por revisão como o resto do build; os comandos atuais estão no repositório da CLI.
Dois limites honestos
Isto é verificação funcional. Os planos gerados incluem, sim, casos de limite e de autorização, então uma execução vai te dizer se um valor fora do intervalo é rejeitado e se uma requisição não autenticada é recusada. Isso não é a mesma coisa que teste de carga ou varredura de vulnerabilidades. Não vai te dizer como o serviço se comporta sob concorrência sustentada, e não é um scanner. São instrumentos separados, e fingir o contrário desperdiça a sua tarde.
Também não vai salvar uma API sem especificação e sem convenções consistentes. A descoberta consegue enumerar endpoints, mas decidir o que significa correto em cada um deles continua sendo um julgamento, e alguém do seu time precisa fazê-lo. O trabalho é menor do que escrever a suíte na mão, e não é zero.
Como o TestSprite lida com os quatro problemas
Os quatro pontos de falha desta página não são incidentais no teste de API, então eles são tratados como produto em vez de ficarem por conta da sua infraestrutura. O Auto-Authentication recebe o fluxo de login uma vez e busca um token novo antes de cada execução. As Dynamic Variables levam um valor de uma resposta para a requisição seguinte. As Dependency Chains derivam uma ordem de execução segura a partir do que cada caso precisa e produz. O Auto-Cleanup remove exatamente os registros que uma execução criou, que é a versão que sobrevive à execução em paralelo.
A cobertura também vem de outro lugar. O API Discovery enumera o que o serviço em execução realmente expõe, a Plan Generation propõe casos nas categorias funcional, de schema, de autorização, de tratamento de erros e de limites, e você os refina em linguagem natural. O resultado inclui os casos negativos que ninguém escreve no fim de uma sprint.
Onde isso te deixa: uma suíte que continua funcionando conforme a API muda, sem um conftest nem um diretório de apoio que só uma pessoa entende. Capacidade de carga e varredura de vulnerabilidades continuam sendo instrumentos separados.
Preciso de uma especificação OpenAPI para começar?
Ajuda, mas não é obrigatório. Um documento de requisitos de produto mais uma passada de descoberta sobre o serviço em execução já te dá um plano utilizável. A especificação melhora principalmente a qualidade do primeiro conjunto de casos gerados.
Qual a diferença entre isso e uma coleção de requisições salvas?
Uma coleção guarda requisições. Os quatro problemas deste artigo são sobre o estado entre as requisições: sessão, valores capturados, ordenação e limpeza. Dá para fazer uma coleção lidar com isso com bastante scripting, que é exatamente o custo de manutenção que a maioria dos times está tentando evitar.
Dá para testar uma API que não é acessível publicamente?
Depende de qual lado você quer dizer. A CLI consegue criar um túnel até uma aplicação rodando na sua própria máquina, então um frontend em localhost está coberto. Esse modo local é só para frontend, então um serviço de backend que não é acessível pela internet ainda precisa de uma URL acessível para as execuções de API. Vale confirmar isso no seu ambiente de staging antes de planejar a adoção.
E APIs atrás de um login com autenticação multifator?
Use uma conta de teste dedicada, sem o segundo fator, restrita ao ambiente de teste. Automatizar um segundo fator real é possível e quase sempre é mais frágil do que vale a pena.
Quantos endpoints a primeira suíte deve cobrir?
Cubra os fluxos que te deixam nervoso antes de um release, não os endpoints mais fáceis de escrever. Dez casos relevantes que rodam a cada pull request valem mais que duzentos que ninguém lê.
Cada um dos quatro tem a própria página na documentação de teste de API se você quiser a mecânica em vez do raciocínio.
Projete desde o primeiro dia pensando em sessão, estado, ordem e limpeza.
Esses quatro são o que transforma uma suíte de API que funciona em uma suíte abandonada. Trate cada um deles de forma explícita, rode a suíte a partir do seu pipeline em vez de na mão, e mantenha o escopo nos fluxos com que você realmente se preocupa.