Estruturação de Testes de API
Tags: testing, api, architecture, automation
Published: 2023-09-12 19:38:18

## 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:



![Arquitetura em camadas, divisão lógica](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kv4yi5b3am5jdh0vk27d.png)


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.


![Divisão lógica dos testes de API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/udhyl24oo6j2abl9kciv.png)


### 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.

{% gist https://gist.github.com/rafaelbercam/2905a9f91ed6cda4f50145b98da18bcf %}

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

{% gist https://gist.github.com/rafaelbercam/73a23e65d3d28d75da625c27137d82c5 %}


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](https://en.wikipedia.org/wiki/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.

{% gist https://gist.github.com/rafaelbercam/7d045f50d7e1dd4f9494ee1e01a76efd %}

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.

{% github https://github.com/rafaelbercam/boilerplates %}

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
* https://www.linkedin.com/in/rafaelbercam/
* https://github.com/rafaelbercam
* https://dev.to/rafaelbercam