---
title: "Testes com pytest: Guia Completo"
url: "https://python.dev.br/guias/testes-com-pytest/"
markdown_url: "https://python.dev.br/guias/testes-com-pytest.MD"
description: "Aprenda a testar seu código Python com pytest. Fixtures, parametrize, mocking, cobertura de testes e boas práticas para projetos reais"
date: "2025-11-22"
author: ""
---

# Testes com pytest: Guia Completo

Aprenda a testar seu código Python com pytest. Fixtures, parametrize, mocking, cobertura de testes e boas práticas para projetos reais


## Introdução

Testar código é uma das habilidades mais importantes para qualquer desenvolvedor Python. O pytest é o framework de testes mais popular do ecossistema Python, usado por projetos como Django, Flask e Requests. Ele é simples para começar e poderoso o suficiente para projetos complexos.

Neste guia, vamos aprender pytest do básico ao avançado, cobrindo fixtures, parametrize, mocking e cobertura de testes.

## Instalação

Crie um ambiente virtual e instale o pytest:

```bash
python3 -m venv venv
source venv/bin/activate
pip install pytest
```

Verifique a instalação:

```bash
pytest --version
```

## Seu primeiro teste

Crie um arquivo `calculadora.py`:

```python
def somar(a, b):
    return a + b


def dividir(a, b):
    if b == 0:
        raise ValueError("Divisao por zero nao permitida")
    return a / b


def eh_par(numero):
    return numero % 2 == 0
```

Agora crie `test_calculadora.py`:

```python
from calculadora import somar, dividir, eh_par
import pytest


def test_somar_positivos():
    assert somar(2, 3) == 5


def test_somar_negativos():
    assert somar(-1, -2) == -3


def test_dividir():
    assert dividir(10, 2) == 5.0


def test_dividir_por_zero():
    with pytest.raises(ValueError):
        dividir(10, 0)


def test_eh_par():
    assert eh_par(4) is True
    assert eh_par(7) is False
```

Execute os testes:

```bash
pytest
```

O pytest descobre automaticamente arquivos que começam com `test_` e funções que começam com `test_`. A saída mostra pontos verdes para testes que passaram e F vermelho para falhas.

## Opções úteis da linha de comando

```bash
# Saida detalhada (verbose)
pytest -v

# Parar no primeiro erro
pytest -x

# Executar apenas um arquivo
pytest test_calculadora.py

# Executar apenas um teste especifico
pytest test_calculadora.py::test_somar_positivos

# Filtrar por nome (executa testes que contem "dividir")
pytest -k "dividir"

# Mostrar prints durante os testes
pytest -s
```

## Organizando testes

A estrutura recomendada para projetos maiores:

```
meu-projeto/
    src/
        meu_pacote/
            __init__.py
            servicos.py
            modelos.py
    tests/
        __init__.py
        test_servicos.py
        test_modelos.py
    pyproject.toml
```

Configure no `pyproject.toml`:

```toml
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
```

## Fixtures

Fixtures são funções que preparam dados ou recursos para os testes. Elas substituem o setup/teardown de outros frameworks:

```python
import pytest


@pytest.fixture
def lista_de_usuarios():
    return [
        {"nome": "Ana", "idade": 28},
        {"nome": "Carlos", "idade": 35},
        {"nome": "Maria", "idade": 22},
    ]


def test_quantidade_usuarios(lista_de_usuarios):
    assert len(lista_de_usuarios) == 3


def test_usuario_mais_novo(lista_de_usuarios):
    mais_novo = min(lista_de_usuarios, key=lambda u: u["idade"])
    assert mais_novo["nome"] == "Maria"
```

O pytest injeta a fixture automaticamente quando o nome do parâmetro corresponde ao nome da fixture.

### Fixtures com escopo

Por padrão, fixtures são criadas para cada teste. Você pode alterar o escopo:

```python
@pytest.fixture(scope="module")
def conexao_banco():
    """Criada uma vez por modulo de teste."""
    conexao = criar_conexao()
    yield conexao
    conexao.fechar()


@pytest.fixture(scope="session")
def configuracao():
    """Criada uma vez por sessao inteira de testes."""
    return carregar_configuracao()
```

O `yield` permite executar código de limpeza após o teste terminar.

### conftest.py

Fixtures compartilhadas entre vários arquivos de teste ficam no `conftest.py`:

```python
# tests/conftest.py
import pytest


@pytest.fixture
def cliente_api():
    from meu_pacote.cliente import ClienteAPI
    return ClienteAPI(base_url="http://localhost:8000")
```

Todos os testes na mesma pasta (e subpastas) podem usar fixtures definidas no conftest.py sem importação.

## Parametrize

O decorator `@pytest.mark.parametrize` permite rodar o mesmo teste com diferentes entradas:

```python
import pytest
from calculadora import somar, eh_par


@pytest.mark.parametrize("a, b, esperado", [
    (1, 2, 3),
    (0, 0, 0),
    (-1, 1, 0),
    (100, 200, 300),
    (0.1, 0.2, pytest.approx(0.3)),
])
def test_somar(a, b, esperado):
    assert somar(a, b) == esperado


@pytest.mark.parametrize("numero, esperado", [
    (2, True),
    (3, False),
    (0, True),
    (-4, True),
    (101, False),
])
def test_eh_par(numero, esperado):
    assert eh_par(numero) == esperado
```

Note o uso de `pytest.approx()` para comparar floats, evitando problemas de precisão de ponto flutuante.

## Mocking

Mocking substitui partes do código por objetos simulados durante os testes. Isso é essencial para testar código que faz chamadas de rede, acessa banco de dados ou depende de serviços externos:

```python
from unittest.mock import patch, MagicMock


def buscar_dados_api(url):
    import requests
    resposta = requests.get(url)
    return resposta.json()


def test_buscar_dados_api():
    dados_falsos = {"nome": "Python", "versao": "3.12"}

    with patch("requests.get") as mock_get:
        mock_get.return_value = MagicMock(
            json=MagicMock(return_value=dados_falsos)
        )

        resultado = buscar_dados_api("https://api.exemplo.com/dados")
        assert resultado["nome"] == "Python"
        mock_get.assert_called_once_with("https://api.exemplo.com/dados")
```

## Testando exceções

```python
import pytest


def test_divisao_por_zero():
    with pytest.raises(ValueError) as exc_info:
        dividir(10, 0)

    assert "zero" in str(exc_info.value)


def test_tipo_invalido():
    with pytest.raises(TypeError):
        somar("texto", 5)
```

## Cobertura de testes

Instale o plugin de cobertura:

```bash
pip install pytest-cov
```

Execute os testes com relatório de cobertura:

```bash
pytest --cov=src --cov-report=term-missing
```

A saída mostra quais linhas do código não estão cobertas por testes. Para gerar um relatório HTML detalhado:

```bash
pytest --cov=src --cov-report=html
```

Abra `htmlcov/index.html` no navegador para ver um relatório visual.

## Markers personalizados

Crie markers para categorizar testes:

```python
import pytest


@pytest.mark.lento
def test_processamento_grande():
    # Teste que demora muito
    pass


@pytest.mark.integracao
def test_conexao_banco():
    # Teste de integração
    pass
```

Execute apenas testes de uma categoria:

```bash
pytest -m "not lento"
pytest -m integracao
```

Registre os markers no `pyproject.toml` para evitar avisos:

```toml
[tool.pytest.ini_options]
markers = [
    "lento: testes que demoram para executar",
    "integracao: testes de integração com serviços externos",
]
```

## Boas práticas

- Nomeie testes de forma descritiva: `test_somar_numeros_negativos_retorna_valor_correto`
- Cada teste deve verificar apenas uma coisa
- Testes devem ser independentes entre si
- Use fixtures para evitar repetição de setup
- Mantenha testes rápidos: moque dependências externas
- Execute testes antes de cada commit
- Busque cobertura acima de 80%, mas foque na qualidade, não apenas no número

## Conclusão

O pytest é uma ferramenta poderosa que torna os testes em Python simples e agradáveis. Com fixtures, parametrize e mocking, você pode testar qualquer tipo de código de forma eficiente. Investir tempo em testes automatizados economiza horas de debugging e aumenta a confiança no código que você entrega. Comece com testes simples e evolua conforme a complexidade do projeto aumenta.

Curioso sobre como outras linguagens abordam testes? Veja como funciona o <a href="https://golang.com.br/blog/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', {source: 'python.dev.br', target: 'golang.com.br', content: 'testes-com-pytest'})">testing nativo do Go</a> e o sistema de testes integrado do <a href="https://rustlang.com.br/blog/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', {source: 'python.dev.br', target: 'rustlang.com.br', content: 'testes-com-pytest'})">Rust no rustlang.com.br</a>.
