Introdução aos Testes de API
APIs (Application Programming Interfaces) são a espinha dorsal da arquitetura moderna de software. Testar APIs de forma eficaz garante que os serviços se comuniquem corretamente, retornem dados esperados e tratem erros apropriadamente.
Por Que Testar APIs?
| Benefício | Impacto |
|---|---|
| Detecção precoce de bugs | APIs são gateway para frontend |
| Integração confiável | Microserviços se comunicam via API |
| Performance | API performance afeta toda a aplicação |
| Documentação viva | Contratos de API testados |
| Regression testing | Mudanças não quebram integrações |
Tipos de Testes de API
1. Testes de Funcionalidade
Verificam se a API funciona conforme especificado.
import requests
import pytest
BASE_URL = "https://api.exemplo.com/v1"
class TestUsuarioAPI:
def test_criar_usuario(self):
"""Deve criar usuário com sucesso"""
payload = {
"nome": "João Silva",
"email": "joao@exemplo.com",
"senha": "Senha@123"
}
response = requests.post(
f"{BASE_URL}/usuarios",
json=payload,
headers={"Content-Type": "application/json"}
)
assert response.status_code == 201
data = response.json()
assert data["nome"] == payload["nome"]
assert data["email"] == payload["email"]
assert "id" in data
assert "senha" not in data # Não retorna senha!
def test_criar_usuario_email_duplicado(self):
"""Deve falhar quando email já existe"""
payload = {
"nome": "João Silva",
"email": "joao@exemplo.com", # Já existe
"senha": "Senha@123"
}
response = requests.post(
f"{BASE_URL}/usuarios",
json=payload
)
assert response.status_code == 409 # Conflict
assert "email" in response.json()["erro"].lower()
def test_buscar_usuario_por_id(self):
"""Deve retornar usuário existente"""
response = requests.get(f"{BASE_URL}/usuarios/1")
assert response.status_code == 200
data = response.json()
assert data["id"] == 1
assert "email" in data
def test_buscar_usuario_inexistente(self):
"""Deve retornar 404 para usuário inexistente"""
response = requests.get(f"{BASE_URL}/usuarios/99999")
assert response.status_code == 404
2. Testes de Contrato
Verificam se a API está em conformidade com seu contrato (OpenAPI/Swagger).
import pytest
from jsonschema import validate
schema = {
"type": "object",
"required": ["id", "nome", "email"],
"properties": {
"id": {"type": "integer"},
"nome": {"type": "string", "minLength": 1},
"email": {"type": "string", "format": "email"}
}
}
def test_usuario_response_match_schema():
response = requests.get(f"{BASE_URL}/usuarios/1")
data = response.json()
validate(instance=data, schema=schema)
3. Testes de Performance
import time
import concurrent.futures
def test_api_response_time():
"""Tempo de resposta deve ser < 500ms"""
start = time.time()
response = requests.get(f"{BASE_URL}/usuarios/1")
elapsed = (time.time() - start) * 1000
assert response.status_code == 200
assert elapsed < 500, f"Response took {elapsed}ms"
def test_concurrent_requests():
"""API deve suportar 100 requisições simultâneas"""
def make_request():
response = requests.get(f"{BASE_URL}/health")
return response.status_code == 200
with concurrent.futures.ThreadPoolExecutor(max_workers=100) as executor:
futures = [executor.submit(make_request) for _ in range(100)]
results = [f.result() for f in concurrent.futures.as_completed(futures)]
success_rate = sum(results) / len(results)
assert success_rate > 0.95, f"Only {success_rate*100}{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} succeeded"
4. Testes de Segurança
def test_sql_injection_prevention():
"""API deve prevenir SQL injection"""
payload = {"email": "admin' OR '1'='1"}
response = requests.post(f"{BASE_URL}/usuarios", json=payload)
# Não deve retornar dados sensíveis
assert response.status_code in [400, 422]
def test_xss_in_input(self):
"""API deve sanitizar inputs"""
payload = {"nome": "alert('xss')"}
response = requests.post(f"{BASE_URL}/usuarios", json=payload)
if response.status_code == 201:
data = response.json()
assert "" not in data["nome"]
def test_rate_limiting(self):
"""API deve limitar requisições excessivas"""
responses = []
for _ in range(105): # Limite é 100
response = requests.get(f"{BASE_URL}/usuarios/1")
responses.append(response.status_code)
# Últimas 5 devem retornar 429 (Too Many Requests)
assert 429 in responses[-5:]
GraphQL: Testando APIs Modernas
Queries
import requests
GRAPHQL_URL = "https://api.exemplo.com/graphql"
class TestGraphQL:
def test_query_basica(self):
"""Deve buscar dados com query GraphQL"""
query = """
query {
usuario(id: 1) {
id
nome
email
}
}
"""
response = requests.post(
GRAPHQL_URL,
json={"query": query}
)
assert response.status_code == 200
data = response.json()
assert "data" in data
assert "usuario" in data["data"]
assert data["data"]["usuario"]["nome"]
def test_query_com_parametros(self):
"""Deve filtrar com argumentos"""
query = """
query($limite: Int) {
produtos(limit: $limite) {
id
nome
preco
}
}
"""
response = requests.post(
GRAPHQL_URL,
json={
"query": query,
"variables": {"limite": 5}
}
)
data = response.json()
assert len(data["data"]["produtos"]) <= 5
def test_mutation_inserir(self):
"""Deve criar dado com mutation"""
mutation = """
mutation {
criarUsuario(input: {
nome: "Maria Silva"
email: "maria@exemplo.com"
senha: "Senha@123"
}) {
id
nome
email
}
}
"""
response = requests.post(GRAPHQL_URL, json={"query": mutation})
assert response.status_code == 200
data = response.json()
assert "errors" not in data
assert data["data"]["criarUsuario"]["nome"] == "Maria Silva"
Introspection para Validação
def test_graphql_introspection():
"""Deve permitir introspection do schema"""
query = """
{
__schema {
types {
name
kind
}
}
}
"""
response = requests.post(GRAPHQL_URL, json={"query": query})
data = response.json()
assert "data" in data
type_names = [t["name"] for t in data["data"]["__schema"]["types"]]
assert "Query" in type_names
Microserviços: Testando em Ambientes Distribuídos
Service Mesh Testing
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ API GW │────▶│ Auth Svc │────▶│ User Svc │
│ :8080 │ │ :8081 │ │ :8082 │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Product │ │ Database │
│ Svc :8083 │ │ :5432 │
└─────────────┘ └─────────────┘
Testes de Contrato com Pact
# Provider (microserviço de usuários)
from pact import Consumer, Provider
pact = Consumer('API-Gateway').has_pact_with(Provider('User-Service'))
pact.start_service()
try:
# Definir expectativas
(pact
.given('user with id 1 exists')
.upon_receiving('a request for user 1')
.with_request('GET', '/users/1')
.will_respond_with(200, body={
'id': 1,
'name': 'Test User',
'email': 'test@example.com'
})
.verify())
# Verificar provider
from pact.matchers import like, Term
(pact
.given('user exists')
.upon_receiving('a request for users')
.with_request('GET', '/users')
.will_respond_with(200, body=[
like({
'id': 1,
'name': Term('w+', 'Test User'),
'email': Term(r'[w.]+@[w.]+', 'test@example.com')
})
])
.verify())
finally:
pact.stop_service()
Contrato Consumer-Driven
// contract.json (gerado pelo consumer)
{
"consumer": {
"name": "Order-Service"
},
"provider": {
"name": "User-Service"
},
"interactions": [
{
"description": "Get user details",
"request": {
"method": "GET",
"path": "/users/1"
},
"response": {
"status": 200,
"body": {
"id": 1,
"name": "John",
"email": "john@example.com"
}
}
}
]
}
TestContainers para Microserviços
import pytest
from testcontainers.postgres import PostgresContainer
from testcontainers.redis import RedisContainer
@pytest.fixture(scope="module")
def postgres():
with PostgresContainer("postgres:15") as pg:
yield pg
@pytest.fixture(scope="module")
def redis():
with RedisContainer("redis:7") as rd:
yield rd
@pytest.fixture(scope="module")
def user_service(postgres, redis):
with DockerCompose(".") as compose:
# Start user-service
compose.exec("user-service", "python", "manage.py", "migrate")
port = compose.expose("user-service", 8080)
yield f"http://localhost:{port}"
Postman: Collection Runner
Collection Structure
{
"info": {
"name": "E-commerce API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Auth",
"item": [
{
"name": "Login",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('Status 200', () => {",
" pm.response.to.have.status(200);",
"});",
"pm.test('Token received', () => {",
" const jsonData = pm.response.json();",
" pm.collectionVariables.set('token', jsonData.token);",
"});"
]
}
}
],
"request": {
"method": "POST",
"url": "{{baseUrl}}/auth/login",
"body": {
"mode": "raw",
"raw": "{"email":"test@test.com","password":"test123"}"
}
}
}
]
},
{
"name": "Users",
"item": [
{
"name": "Get All Users",
"request": {
"auth": {
"type": "bearer",
"bearer": [{"key": "token", "value": "{{token}}"}]
},
"method": "GET",
"url": "{{baseUrl}}/users"
}
}
]
}
]
}
Newman CLI
# Executar collection
newman run ecommerce-api.postman_collection.json
--environment dev.postman_environment.json
--reporters html,json
--reporter-json-export results.json
--reporter-html-export report.html
# Com CI/CD
newman run collection.json
--environment ${{ secrets.POSTMAN_ENV }}
--iteration-count 1
--bail
Frameworks de Teste de API
RestAssured (Java)
import static io.restassured.RestAssured.*;
import static io.restassured.matcher.RestAssuredMatchers.*;
import static org.hamcrest.Matchers.*;
class UserApiTest {
@BeforeAll
static void setup() {
RestAssured.baseURI = "https://api.exemplo.com/v1";
}
@Test
void criarUsuario() {
given()
.contentType("application/json")
.body("""
{
"nome": "João Silva",
"email": "joao@exemplo.com",
"senha": "Senha@123"
}
""")
.when()
.post("/usuarios")
.then()
.statusCode(201)
.body("nome", equalTo("João Silva"))
.body("email", equalTo("joao@exemplo.com"))
.body("id", notNullValue())
.body("senha", nullValue());
}
@Test
void validarSchema() {
given()
.get("/usuarios/1")
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("schemas/usuario.json"));
}
}
Pytest + requests
# pytest.ini
[pytest]
addopts = -v --tb=short --strict-markers
markers =
smoke: Smoke tests
integration: Integration tests
slow: Slow running tests
# conftest.py
import pytest
import requests
@pytest.fixture
def api_client():
class APIClient:
base_url = "https://api.exemplo.com/v1"
def __init__(self):
self.session = requests.Session()
def create_user(self, data):
return self.session.post(f"{self.base_url}/usuarios", json=data)
def get_user(self, user_id):
return self.session.get(f"{self.base_url}/usuarios/{user_id}")
def delete_user(self, user_id):
return self.session.delete(f"{self.base_url}/usuarios/{user_id}")
return APIClient()
@pytest.fixture
def clean_user(api_client):
"""Cria usuário para teste e limpa depois"""
user_data = {
"nome": "Test User",
"email": f"test_{uuid4()}@exemplo.com",
"senha": "Senha@123"
}
response = api_client.create_user(user_data)
user_id = response.json()["id"]
yield user_id
# Cleanup
api_client.delete_user(user_id)
# test_api.py
@pytest.mark.integration
def test_criar_e_buscar_usuario(api_client):
# Create
user_data = {"nome": "Test", "email": "test@ex.com", "senha": "123"}
response = api_client.create_user(user_data)
assert response.status_code == 201
user_id = response.json()["id"]
# Read
response = api_client.get_user(user_id)
assert response.status_code == 200
assert response.json()["nome"] == "Test"
Conclusão
Testar APIs é fundamental para garantir qualidade em arquiteturas modernas. As chaves são:
- Cobertura completa – Funcional, contrato, performance, segurança
- Automação – CI/CD com collections automatizadas
- Contratos – Consumer-driven contracts para microservices
- Isolamento – TestContainers para dependências
- Monitoramento – API health checks em produção
FAQ
P: Postman substitui testes de código?
R: Não. Postman é excelente para exploratory e collections, mas testes em código são mais versionáveis e reutilizáveis.
P: Como testar APIs GraphQL?
R: Use GraphQL client libraries (gql, strawberry) e valide queries, mutations e subscriptions.
P: Contract testing é necessário?
R: Essencial em microservices. Consumer-driven contracts (Pact) garantem que mudanças não quebram consumidores.
P: Quanto coverage é suficiente?
R: 100{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} dos endpoints críticos, pelo menos 80{6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442} dos endpoints públicos.
