Framework de Testes Automatizados com Pytest API: Tutorial Prático

INEZA Felin-Michel

INEZA Felin-Michel

22 maio 2026

Framework de Testes Automatizados com Pytest API: Tutorial Prático

Apidog para empresas

Implantação local

SSO & RBAC

Conforme SOC 2

Explorar Apidog Enterprise

Desenvolvedores Python recorrem ao pytest porque ele não atrapalha. Um teste é apenas uma função cujo nome começa com test_, uma asserção é uma declaração assert simples, e o executor faz o resto. Combine-o com a biblioteca requests e você terá um framework completo e focado em código para automatizar testes de API, sem formalidades pesadas.

Este tutorial mostra como construir um conjunto de testes de API pytest real. Você configurará o projeto, escreverá seu primeiro teste de requisição, compartilhará a lógica de configuração com fixtures, executará o mesmo teste contra várias entradas com parametrize e fará asserções em relação ao status da resposta, corpo e Schema JSON. Cada exemplo usa uma API de estilo público realista para que você possa adaptar o código diretamente.

Configurando o projeto

Instale as duas bibliotecas que você precisa em um ambiente virtual:

python -m venv .venv
source .venv/bin/activate
pip install pytest requests jsonschema

Um layout limpo mantém o conjunto de testes fácil de manter à medida que cresce:

api-tests/
  conftest.py        # shared fixtures
  test_users.py      # tests for the users endpoints
  test_orders.py     # tests for the orders endpoints
  pytest.ini         # configuration

O Pytest descobre testes automaticamente. Os arquivos devem começar com test_ ou terminar com _test.py, as funções devem começar com test_ e as classes de teste devem começar com Test e não ter um método __init__. Siga essas regras e você nunca precisará configurar a descoberta manualmente. Se a automação de testes como disciplina é nova para você, nosso guia sobre o que é teste automatizado contextualiza o assunto.

Por que o pytest especificamente para testes de API? A biblioteca requests lida com o HTTP, e o pytest lida com todo o resto: descoberta, asserções com saída de falha legível, setup e teardown através de fixtures, execuções orientadas a dados através de parametrize e relatórios. Você monta um framework completo de automação de API a partir de duas bibliotecas pequenas e bem documentadas, em uma linguagem que sua equipe provavelmente já usa para a própria aplicação. Essa proximidade importa. Testes que vivem no mesmo repositório que o código permanecem honestos, porque uma mudança disruptiva e seu teste falho aparecem na mesma pull request.

Escrevendo seu primeiro teste de API

Um teste de API pytest envia uma requisição e faz asserções na resposta. Aqui está um teste para um endpoint de usuários:

import requests

BASE_URL = "https://api.example.com/v1"

def test_get_user_returns_200():
    response = requests.get(f"{BASE_URL}/users/42")
    assert response.status_code == 200

def test_get_user_returns_expected_fields():
    response = requests.get(f"{BASE_URL}/users/42")
    body = response.json()
    assert body["id"] == 42
    assert "email" in body
    assert body["status"] == "active"

Execute o conjunto de testes com pytest -v. Cada assert que falha produz um relatório detalhado mostrando o valor real, o que é uma das melhores características do pytest. Você não precisa de métodos de asserção especiais; o framework reescreve as declarações assert simples para fornecer uma saída rica. Para o conjunto mais amplo de verificações que valem a pena fazer em uma resposta, consulte nosso guia sobre asserções de API.

Compartilhando a configuração com fixtures

Repetir a URL base e uma sessão HTTP em cada teste é um desperdício. Fixtures resolvem isso. Uma fixture é uma função decorada com @pytest.fixture que produz um valor que os testes podem solicitar nomeando-o como um parâmetro.

Coloque fixtures compartilhadas em conftest.py para que todo arquivo de teste possa usá-las sem importar:

# conftest.py
import pytest
import requests

BASE_URL = "https://api.example.com/v1"

@pytest.fixture(scope="session")
def api_session():
    session = requests.Session()
    session.headers.update({"Accept": "application/json"})
    yield session
    session.close()

@pytest.fixture
def auth_token(api_session):
    response = api_session.post(
        f"{BASE_URL}/auth/login",
        json={"email": "qa@example.com", "password": "test-pass"},
    )
    return response.json()["token"]

O argumento scope="session" significa que a sessão é criada uma vez para toda a execução, em vez de por teste. A palavra-chave yield divide a configuração da desmontagem: o código antes de yield é executado primeiro, o código depois é executado quando a fixture sai do escopo. Um teste simplesmente solicita o que precisa:

def test_create_order(api_session, auth_token):
    response = api_session.post(
        f"{BASE_URL}/orders",
        headers={"Authorization": f"Bearer {auth_token}"},
        json={"product_id": 7, "quantity": 2},
    )
    assert response.status_code == 201
    assert response.json()["status"] == "pending"

Fixtures são o substituto moderno do pytest para o estilo mais antigo de setup_function e teardown_function. Elas se compõem de forma limpa, suportam escopos e tornam as dependências explícitas, razão pela qual a documentação oficial de fixtures do pytest as recomenda como abordagem padrão.

Executando um teste contra múltiplas entradas

Endpoints de API geralmente precisam ser verificados contra muitas entradas: valores válidos, valores inválidos e casos de borda. Escrever uma função separada para cada um é tedioso. O decorador @pytest.mark.parametrize executa o corpo de um teste contra uma lista de entradas:

import pytest
import requests

BASE_URL = "https://api.example.com/v1"

@pytest.mark.parametrize("user_id,expected_status", [
    (42, 200),
    (99999, 404),
    (0, 404),
    (-1, 400),
])
def test_get_user_status_codes(api_session, user_id, expected_status):
    response = api_session.get(f"{BASE_URL}/users/{user_id}")
    assert response.status_code == expected_status

Isso produz quatro casos de teste separados a partir de uma única função. Cada um é executado e relatado independentemente, de modo que uma única entrada inválida não esconde as outras. Parametrize é a resposta integrada do pytest para testes orientados por dados. Quando o conjunto de entradas se torna grande, carregue-o de um arquivo; nosso guia sobre testes de API orientados por dados com CSV e JSON aborda esse padrão. Se você não tem certeza de qual código de status cada entrada deve retornar, a referência sobre códigos de status HTTP que as APIs REST devem usar é um companheiro útil.

Fazendo asserções no corpo e schema da resposta

Códigos de status são necessários, mas não suficientes. Uma resposta 200 com um corpo malformado ainda é um bug. Faça asserções diretamente no JSON parseado:

def test_order_response_shape(api_session, auth_token):
    response = api_session.post(
        f"{BASE_URL}/orders",
        headers={"Authorization": f"Bearer {auth_token}"},
        json={"product_id": 7, "quantity": 2},
    )
    body = response.json()
    assert isinstance(body["id"], int)
    assert body["quantity"] == 2
    assert body["total"] > 0
    assert response.elapsed.total_seconds() < 1.0

Para garantias mais fortes, valide o corpo contra um Schema JSON. Isso captura desvios estruturais, como um campo renomeado ou ausente, que as verificações manuais podem perder:

from jsonschema import validate

order_schema = {
    "type": "object",
    "required": ["id", "product_id", "quantity", "status", "total"],
    "properties": {
        "id": {"type": "integer"},
        "product_id": {"type": "integer"},
        "quantity": {"type": "integer", "minimum": 1},
        "status": {"type": "string"},
        "total": {"type": "number"},
    },
}

def test_order_matches_schema(api_session, auth_token):
    response = api_session.post(
        f"{BASE_URL}/orders",
        headers={"Authorization": f"Bearer {auth_token}"},
        json={"product_id": 7, "quantity": 2},
    )
    validate(instance=response.json(), schema=order_schema)

A validação de schema escala melhor do que as asserções campo a campo porque um schema cobre toda a forma da resposta. A biblioteca jsonschema é a escolha padrão, e sua documentação de validação explica as palavras-chave suportadas.

Executando o conjunto de testes na CI

Um conjunto de testes pytest justifica seu valor quando é executado automaticamente. O Pytest retorna um código de saída diferente de zero em caso de falha, o que é exatamente o que um servidor de CI precisa para falhar uma compilação. Emita um relatório JUnit para exibição inline:

pytest -v --junitxml=results.xml

Integre esse comando em uma etapa do GitHub Actions ou qualquer outro pipeline e seus testes de API protegerão cada commit. Nosso passo a passo sobre testes de API em pipelines de CI/CD mostra a configuração completa, incluindo injeção de segredos para tokens e seleção de ambiente.

Dois hábitos de CI mantêm um conjunto de testes pytest confiável. Primeiro, nunca codifique secrets ou URLs de ambiente diretamente nos arquivos de teste. Leia-os de variáveis de ambiente para que o mesmo conjunto de testes seja executado contra o ambiente de staging na CI e localmente sem edições:

import os

BASE_URL = os.environ.get("API_BASE_URL", "https://staging.example.com/v1")

Segundo, execute testes independentes em paralelo para manter o feedback rápido. O plugin pytest-xdist distribui os testes entre os núcleos da CPU com pytest -n auto. As execuções paralelas só funcionam se seus testes não compartilharem estado, o que é mais uma razão pela qual a camada de dados de teste é importante. Um conjunto de testes que depende da ordem de execução falhará imprevisivelmente no momento em que for executado em paralelo.

Mantendo um conjunto de testes pytest de fácil manutenção

Um conjunto de cinquenta testes é fácil. Um conjunto de quinhentos exige disciplina. Três práticas mantêm um framework de API pytest saudável à medida que cresce.

Agrupe testes relacionados em módulos e use classes apenas quando compartilharem configuração, não para decoração. Um arquivo test_orders.py com um conjunto claro de funções é mais legível do que um único arquivo gigante. Use marcadores, registrados em pytest.ini, para categorizar testes e poder executar subconjuntos: @pytest.mark.smoke para uma verificação rápida, @pytest.mark.slow para a varredura completa. Execute o conjunto de smoke em cada commit e o conjunto completo diariamente.

Centralize a configuração. URLs base, schemas e fixtures compartilhadas pertencem a conftest.py ou a um pequeno módulo de configuração, nunca copiados e colados entre arquivos. Quando a URL de staging muda, você deve editar apenas uma linha. A mesma disciplina modular que se aplica a qualquer framework, abordada em nosso guia sobre como escrever scripts de teste automatizados, aplica-se aqui: extraia tudo o que você escreve duas vezes para uma fixture ou função auxiliar.

Quando optar por uma plataforma

Um framework pytest é excelente quando sua equipe escreve em Python e deseja testes que vivam ao lado do código da aplicação. É menos conveniente quando a equipe de QA ou produto precisa contribuir, ou quando você deseja design de teste, mocking e execução em um só lugar sem manter código de "cola".

Apidog preenche essa lacuna. Ele oferece construção visual de testes, validação de schema contra sua especificação OpenAPI, execuções orientadas por dados de CSV e JSON, e um executor CLI para CI, tudo sem escrever código de fixture e asserção manualmente. Muitas equipes utilizam ambos: pytest para cenários com lógica complexa e Apidog para ampla cobertura e para projetar e simular as APIs que o conjunto de testes pytest testa. Você pode baixar o Apidog e comparar as duas abordagens em um endpoint real em uma tarde.

Perguntas frequentes

Por que usar pytest em vez do unittest embutido do Python para testes de API?

O Pytest exige menos boilerplate. Os testes são funções simples, as asserções são declarações assert simples com saída de falha rica, e as fixtures lidam com a configuração de forma mais flexível do que os métodos baseados em classe do unittest. O Pytest também possui um grande ecossistema de plugins e parametrize integrado para testes orientados por dados. Ele ainda pode executar testes existentes no estilo unittest, então a migração é de baixo risco.

Qual a diferença entre uma fixture e parametrize?

Uma fixture fornece um recurso reutilizável, como uma sessão HTTP ou um token de autenticação, para qualquer teste que o solicite. Parametrize executa o mesmo corpo de teste várias vezes contra diferentes valores de entrada. Fixtures compartilham a configuração; parametrize multiplica os casos. Eles combinam bem: um teste parametrizado ainda pode depender de fixtures.

Devo fazer asserções sobre o tempo de resposta em testes de API pytest?

Você pode, usando response.elapsed.total_seconds(), e um limite superior flexível detecta regressões brutas. Mas o pytest é uma ferramenta de teste funcional, não um testador de carga. Para um trabalho real de desempenho, use uma ferramenta dedicada. Mantenha as asserções de tempo generosas para que a variação normal da rede não cause falhas intermitentes.

Como manter os testes de API independentes no pytest?

Dê a cada teste seus próprios dados através de fixtures que criam e limpam recursos, e evite depender da ordem de execução dos testes. O Pytest executa os testes na ordem dos arquivos por padrão, mas um conjunto de testes bem projetado não deve depender disso. Testes independentes podem ser executados em paralelo e falhar isoladamente, o que torna a depuração muito mais fácil.

O pytest pode validar respostas contra uma especificação OpenAPI?

O pytest por si só não valida, mas você pode validar contra um Schema JSON com a biblioteca jsonschema, e existem plugins que verificam respostas contra um documento OpenAPI. Se a validação de schema é central para o seu fluxo de trabalho, uma plataforma como Apidog, que valida automaticamente contra sua especificação OpenAPI, pode economizar o trabalho de configuração do plugin.

Pratique o design de API no Apidog

Descubra uma forma mais fácil de construir e usar APIs

Framework de Testes Automatizados com Pytest API: Tutorial Prático