Resumo
Então, resolvi postar esse conteúdo para trazer um ponto de vista sobre a importância de estruturar seus testes de API e como isso pode tornar seu dia a dia eficiente e organizado.
Essa forma de organização é baseada em estrutura de camadas que são formas de abstrair algumas classes e responsabilidades do seu código, para torná-lo legível, de fácil manutenção e muitas boas práticas.
Como eu sei que isso pode variar de linguagem para linguagem, framework para framework, eu vou exemplificar mostrando em diferentes contextos e uso das principais linguagens do mercado.
Então bora la começar!
Introdução
Testes de API são essenciais para garantir a qualidade e confiabilidade de aplicações que utilizam APIs. Eles permitem verificar se as APIs estão funcionando conforme o esperado, identificando e corrigindo bugs antes que eles cheguem as aplicações ou sistemas clientes (consumers)
Uma boa estrutura para testes de API pode ajudar a tornar o processo de desenvolvimento e manutenção mais eficiente. Ela pode ajudar a organizar os testes, facilitar a identificação de problemas e tornar os testes mais repetíveis (reuso).
Uma boa estrutura para testes de API deve atender aos seguintes objetivos:
- Organização: Os testes devem ser organizados de forma lógica e fácil de entender.
- Eficiência: O processo de execução dos testes deve ser eficiente e rápido.
- Repetibilidade: Os testes devem ser repetíveis, para que os resultados possam ser verificados com confiança (testes independentes).
Estrutura em camadas
Uma estrutura de testes de API em camadas, pode te ajudar a atingir esses objetivos, pois ele divide os testes em diferentes camadas, cada uma com uma responsabilidade específica.
A Arquitetura em camadas é um estilo arquitetural que dentre vários objetivos, o maior dele é organizar as responsabilidades de partes de um software, normalmente criando um isolamento e dando um propósito bem definido a cada camada de forma que a mesma possa ser reutilizável por um nível mais alto ou até substituível. O uso desse conceito é até antigo, amplamente utilizado a algum tempo, porém essa motivação de traze-lo para automação de testes foi devido a grande desorganização nas aplicações corporativas que ja trabalhei, que se perderam em meio a troca de profissionais, descontinuação de uma stack ou até mesmo a reescrita de um mesmo teste com tecnologias diferentes (Sim, existem profissionais que sabem apenas uma ferramenta, e usam a qualquer custo).
Exemplo:
Uma camada física, pode ser convertida em várias camadas lógicas para organizar e traçar responsabilidades de uma aplicação backend.
Então como poderíamos trazer isso para automação dos testes de API? Para isso vamos pensar em um modelo básico, que não necessariamente será o mesmo em todos os casos, mas possuem características semelhantes.
Data
Essa camada é pensada justamente na forma que estamos abstraindo a construção de objetos que usamos em nossas requisições. Um grande ponto de atenção que percebo, é que a maioria dos projetos o payload é criado dentro do teste, ou na camada que realiza a requisição. Isso cria um acoplamento desnecessário e prejudica a manutenção do código, dado que precisa alterar o código em vários pontos em sua manutenção.
Então como podemos abstrair isso?
Simples, basta usarmos uma classe simples que retorna um objeto de acordo com seu construtor ou método que retorna ele para sua classe. O ideal é sempre passar o payload (ou body) como parâmetro da sua requisição.
No exemplo acima é apenas uma abstração de uma classe que por sua vez possui um método new_user_payload
que retorna o objeto user para uma requisição do tipo POST create_user
então, eu deleguei para a classe DataUser
a responsabilidade de criar um objeto.
A Princípio isso pode parecer simples, mas a manutenção do código passa a ser simplificada uma vez que a criação do objeto é concentrada em uma classe, sem a necessidade de repetir o objeto no corpo do teste.
Requests
Esta camada é mais comum vê-la abstraída de forma correta, mas nem sempre é seguida uma boa prática em sua utilização.
Uma vez definido que uma classe irá retornar o resultado de uma requisição Response
então vamos definir essa responsabilidade única para suas funções
Repare que a classe UserRequests
dispõe funções que retornam o resultado das requisições, e recebe por parâmetro o payload (body) ou outros argumentos necessários, para tornar mais genérica sua chamada. Isso fará que sua classe tenha um reuso dentro dos testes.
Setup
Em alguns testes e frameworks, sempre temos a necessidade de abstrair algum dado que não pode ficar hard coding na aplicação, isso tudo porque precisamos realizar alterações nesse valor para deixar nossos testes repetíveis em várias situações.
A mais comum são as trocas de ambientes (de desenvolvimento para homologação para produção, etc..)
Então essas classes ou configurações são responsáveis por garantir que essas informações sejam carregadas de acordo com a necessidade dos nossos testes.
Um bom exemplo, é a base_uri
das nossas API's que podem mudar de acordo com o ambiente.
Neste exemplo eu salvei a informação da ninha url base no arquivo .env
que por sua vez é inicializada através da classe SetupClass
que retorna o valor contido na variável BASE_URL
. Ja nas classes responsáveis por realizar as requisições eu apenas retorno para o ENDPOINT
o valor dessa configuração.
Devemos evitar ao máximo realizar o hard coding em nossos projetos, eles normalmente dificultam a manutenção e podemos até mesmo quebrar os testes quando não nos atentamos a estas alterações.
Importante
Essa maneira de organizar o seu código não é única e exclusiva, mas a iniciativa é exemplificar o quanto um código pode ficar organizado seguindo esse modelo. E por vezes quando mudamos de linguagem ou framework, são disponibilizadas recursos diferentes para as mesmas configurações, então o foco não deve ser em uma linguagem ou ferramenta ok?
Conclusão
A busca por deixar nossos projetos de automação cada vez mais robustos e reaproveitáveis nos leva a pensar em diferentes maneiras de organizar nosso código, quando estamos desenvolvendo de forma colaborativa, a quantidade de conflitos que podemos ter em nossos repositórios sempre são grandes, dado a quantidade de acoplamento que criamos sob nossas automações.
A principal motivação é que este post lhe faça refletir sobre como podemos disponibilizar nosso código de forma objetiva e fácil manutenção e que suas abstrações façam o papel de ajudar a legibilidade do código e não aumente a complexidade de aprendizado.
Deixo aqui um repositório com vários boilerplates do mesmo projeto de automação de API com diferentes linguagens e frameworks que trabalham a mesma ideia.
rafaelbercam / boilerplates
Test automation project boilerplates
Boilerplates Testes Automatizados
O que são?
Em programação de computadores, código boilerplate ou boilerplate se refere a seções de código que devem ser incluídas em muitos lugares com pouca ou nenhuma alteração.
Traduzindo em poucas palavras: é um código padrão ou esqueleto de um código no qual poderá ser reutilizado com pouca alteração ou nenhuma para dar start nos projetos.
Seções
Testes API
- Automação de Testes API com REST-assured - Kotlin
- Automação de Testes API com REST-assured - Java
- Automação de Testes API com Mocha e Chai - TypeScript
- Automação de Testes API com RSpec e HTTParty - Ruby
- Automação de Testes API com Supertest & Jest - TypeScript
- Automação de testes API com Pytest - Python
Testes Web
Testes Mobile
Web Crawler
E não deixe de compartilhar este post e deixar um comentário ou dúvida! Minha intenção é compartilhar um pouco do meu conhecimento!
Até a próxima!
Meus links
Top comments (1)
Olá, eu tenho algumas dúvidas sobre a estrutura pra automação de API, eu tenho um arquivo de dado que vai receber meu payload, certo? Tbm terei um pageObject que vai conter todas as minhas funções, e assim no meu teste eu irei chamar as funções, dados, deixando o código mais limpo, no caso da API voce fez um exemplo pra todos os endpoint, mas nao pra os retornos de cada um, como essa estrura contempla todos os status? Isso seria feito no pageObject, seria feito uma função pra casa status retornado e só no teste seria chamado? Eu achei muito extenso fazer desse jeito. Qual a melhor prática pra manutenção?