🌗 ← Índice

🐍 Python P17 — Testes com pytest


Data: 14/08/2026

Nível: Intermediário (Mês 2 — POO)




🎯 Objetivos





📖 Teoria


Instalar



pip install pytest

O `assert`



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

assert somar(2, 3) == 5        # passa silenciosamente
assert somar(2, 3) == 6        # AssertionError

Primeiro teste — convenções


Arquivo test_calculadora.py:



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

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

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


pytest                      # descobre test_*.py e test_*()
pytest -v                   # verboso
pytest test_calculadora.py  # arquivo específico

Regras de descoberta: arquivos `test_*.py` (ou `*_test.py`), funções `test_*`.

Testar exceções



import pytest

def dividir(a, b):
    if b == 0:
        raise ValueError("Divisão por zero não permitida")
    return a / b

def test_dividir_por_zero():
    with pytest.raises(ValueError, match="Divisão por zero"):
        dividir(10, 0)

`fixtures` — setup compartilhado



import pytest

@pytest.fixture
def cliente():
    """Cria um cliente de teste reutilizável."""
    return {"nome": "Hugo", "saldo": 1000.0}

def test_saldo_inicial(cliente):
    assert cliente["saldo"] == 1000.0

def test_deposito(cliente):
    cliente["saldo"] += 500
    assert cliente["saldo"] == 1500.0

💡 Cada teste recebe uma **cópia nova** da fixture (isolamento entre testes).

`parametrize` — vários casos



import pytest

def classificar(nota):
    if nota >= 7:
        return "aprovado"
    return "reprovado"

@pytest.mark.parametrize("nota,esperado", [
    (10, "aprovado"),
    (7, "aprovado"),
    (6.9, "reprovado"),
    (0, "reprovado"),
])
def test_classificar(nota, esperado):
    assert classificar(nota) == esperado

Roda 4 testes a partir de 1 definição — ótimo para tabelas de casos.

Estrutura de projeto típica



projeto/
├── calculadora.py       # código
└── test_calculadora.py  # testes (mesma pasta, ou tests/)

Separação maior: `src/` + `tests/` com `conftest.py` para fixtures globais.

Mock básico (testar sem tocar a rede)



import pytest
from unittest.mock import patch

def buscar_usuario(cliente_http, id):
    resposta = cliente_http.get(f"/usuarios/{id}")
    return resposta.json()

def test_buscar_usuario():
    classe_fake = type("Resp", (), {"json": lambda self: {"id": 1, "nome": "Ana"}})
    cliente_fake = type("Cli", (), {"get": lambda self, url: classe_fake()})

    resultado = buscar_usuario(cliente_fake, 1)
    assert resultado == {"id": 1, "nome": "Ana"}

💡 `unittest.mock.patch` permite "congelar" funções externas (ex: `requests.get`) — assunto da próxima profundidade.



✍️ Exercícios


  1. 1. Escreva uma função `eh_palindromo(texto)` e 4 testes (palíndromo simples, frase com espaços, não-palíndromo, string vazia).
  2. 2. Use `parametrize` para testar uma função `calcular_desconto(preco, percentual)` com 5 casos (incluindo erro: percentual > 100 deve levantar `ValueError`).
  3. 3. Crie uma fixture `contas` que devolve 3 contas e use-a em 2 testes diferentes.



🧪 Desafio


Escreva testes completos para a classe ClienteGitHub da Aula P15:



Dica: use unittest.mock.patch("requests.Session.get") com MagicMock.




✅ Correção



# test_cliente_github.py
import pytest
from unittest.mock import patch, MagicMock


class ClienteGitHub:
    def __init__(self, token=None):
        import requests
        self.sessao = requests.Session()

    def buscar_repos(self, query, limite=5):
        try:
            resposta = self.sessao.get(
                "https://api.github.com/search/repositories",
                params={"q": query, "per_page": limite},
                timeout=10,
            )
            resposta.raise_for_status()
        except Exception:
            return []
        return resposta.json().get("items", [])


def test_buscar_repos_sucesso():
    resposta_fake = MagicMock()
    resposta_fake.raise_for_status.return_value = None
    resposta_fake.json.return_value = {"items": [{"full_name": "teste/repo"}]}

    with patch("requests.Session.get", return_value=resposta_fake):
        cliente = ClienteGitHub()
        resultado = cliente.buscar_repos("python")

    assert len(resultado) == 1
    assert resultado[0]["full_name"] == "teste/repo"


def test_buscar_repos_erro_rede():
    import requests
    with patch("requests.Session.get", side_effect=requests.exceptions.ConnectionError):
        cliente = ClienteGitHub()
        resultado = cliente.buscar_repos("python")

    assert resultado == []


def test_repo_info_campos():
    resposta_fake = MagicMock()
    resposta_fake.raise_for_status.return_value = None
    resposta_fake.json.return_value = {
        "full_name": "dono/repo", "description": "desc",
        "stargazers_count": 10, "language": "Python", "html_url": "http://x",
    }

    with patch("requests.Session.get", return_value=resposta_fake):
        cliente = ClienteGitHub()
        info = cliente.repo_info("dono", "repo")

    assert set(info.keys()) == {"nome", "descricao", "estrelas", "linguagem", "url"}

💡 Resultado esperado: 3 passed in 0.1s — testes rápidos, sem rede, sem chave de API.





HB Estudos · ← Voltar ao índice de Python