Communitygithub.com

python-testing

Estratégias de teste em Python usando pytest, metodologia TDD, fixtures, mocks, parametrização e requisitos de cobertura. Use ao escrever testes pytest — fixtures, mocks, parametrização ou cobertura.

O que é python-testing?

Este skill fornece um guia abrangente para testes em Python com pytest, cobrindo desde os fundamentos até técnicas avançadas. Ele é projetado para desenvolvedores que desejam aplicar a metodologia TDD (Test-Driven Development) de forma eficaz, garantindo que o código seja testado antes de ser escrito. O skill inclui exemplos práticos de fixtures com diferentes escopos (função, módulo, sessão), parametrização de testes para cobrir múltiplos cenários, mocking e patching para simular dependências externas, e uso de marcadores personalizados para seleção de testes. Também aborda testes de código assíncrono com pytest-asyncio, tratamento de exceções e efeitos colaterais como operações de arquivo. O objetivo é ajudar o usuário a criar suítes de teste robustas e confiáveis, com cobertura de código alvo de 80% ou mais, seguindo as melhores práticas da comunidade Python. O skill é compatível com qualquer ambiente de desenvolvimento Python que utilize pytest e pode ser integrado a ferramentas de CI/CD para validação contínua.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/python-testing

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Padrões de Teste em Python

Estratégias abrangentes de teste para aplicações Python usando pytest, metodologia TDD e melhores práticas.

Quando Ativar

  • Escrevendo novo código Python (siga TDD: vermelho, verde, refatorar)
  • Projetando suítes de teste para projetos Python
  • Revisando cobertura de teste em Python
  • Configurando infraestrutura de teste

Filosofia Central de Teste

Desenvolvimento Orientado a Testes (TDD)

Sempre siga o ciclo TDD:

  1. VERMELHO: Escreva um teste que falha para o comportamento desejado
  2. VERDE: Escreva o código mínimo para fazer o teste passar
  3. REFATORAR: Melhore o código mantendo os testes verdes
# Passo 1: Escreva um teste que falha (VERMELHO)
def test_somar_numeros():
    resultado = somar(2, 3)
    assert resultado == 5

# Passo 2: Escreva a implementação mínima (VERDE)
def somar(a, b):
    return a + b

# Passo 3: Refatore se necessário (REFATORAR)

Requisitos de Cobertura

  • Alvo: 80%+ de cobertura de código
  • Caminhos críticos: 100% de cobertura obrigatória
  • Use pytest --cov para medir a cobertura
pytest --cov=meupacote --cov-report=term-missing --cov-report=html

Fundamentos do pytest

Estrutura Básica de Teste

import pytest

def test_adicao():
    """Testa adição básica."""
    assert 2 + 2 == 4

def test_string_maiuscula():
    """Testa conversão para maiúsculas."""
    texto = "ola"
    assert texto.upper() == "OLA"

def test_lista_append():
    """Testa append em lista."""
    itens = [1, 2, 3]
    itens.append(4)
    assert 4 in itens
    assert len(itens) == 4

Asserções

# Igualdade
assert resultado == esperado

# Desigualdade
assert resultado != inesperado

# Valor verdadeiro/falso
assert resultado  # Verdadeiro
assert not resultado  # Falso
assert resultado is True  # Exatamente True
assert resultado is False  # Exatamente False
assert resultado is None  # Exatamente None

# Pertencimento
assert item in colecao
assert item not in colecao

# Comparações
assert resultado > 0
assert 0 <= resultado <= 100

# Verificação de tipo
assert isinstance(resultado, str)

# Teste de exceção (abordagem preferida)
with pytest.raises(ValueError):
    raise ValueError("mensagem de erro")

# Verificar mensagem da exceção
with pytest.raises(ValueError, match="entrada inválida"):
    raise ValueError("entrada inválida fornecida")

# Verificar atributos da exceção
with pytest.raises(ValueError) as exc_info:
    raise ValueError("mensagem de erro")
assert str(exc_info.value) == "mensagem de erro"

Fixtures

Uso Básico de Fixture

import pytest

@pytest.fixture
def dados_exemplo():
    """Fixture fornecendo dados de exemplo."""
    return {"nome": "Alice", "idade": 30}

def test_dados_exemplo(dados_exemplo):
    """Teste usando a fixture."""
    assert dados_exemplo["nome"] == "Alice"
    assert dados_exemplo["idade"] == 30

Fixture com Setup/Teardown

@pytest.fixture
def banco_dados():
    """Fixture com setup e teardown."""
    # Setup
    db = Database(":memory:")
    db.create_tables()
    db.insert_test_data()

    yield db  # Fornece ao teste

    # Teardown
    db.close()

def test_consulta_banco_dados(banco_dados):
    """Testa operações no banco de dados."""
    resultado = banco_dados.query("SELECT * FROM usuarios")
    assert len(resultado) > 0

Escopos de Fixture

# Escopo de função (padrão) - executa para cada teste
@pytest.fixture
def arquivo_temporario():
    with open("temp.txt", "w") as f:
        yield f
    os.remove("temp.txt")

# Escopo de módulo - executa uma vez por módulo
@pytest.fixture(scope="module")
def banco_modulo():
    db = Database(":memory:")
    db.create_tables()
    yield db
    db.close()

# Escopo de sessão - executa uma vez por sessão de teste
@pytest.fixture(scope="session")
def recurso_compartilhado():
    resource = ExpensiveResource()
    yield resource
    resource.cleanup()

Fixture com Parâmetros

@pytest.fixture(params=[1, 2, 3])
def numero(request):
    """Fixture parametrizada."""
    return request.param

def test_numeros(numero):
    """Teste executa 3 vezes, uma para cada parâmetro."""
    assert numero > 0

Usando Múltiplas Fixtures

@pytest.fixture
def usuario():
    return User(id=1, name="Alice")

@pytest.fixture
def admin():
    return User(id=2, name="Admin", role="admin")

def test_interacao_usuario_admin(usuario, admin):
    """Teste usando múltiplas fixtures."""
    assert admin.can_manage(usuario)

Fixtures Autouse

@pytest.fixture(autouse=True)
def resetar_config():
    """Executa automaticamente antes de cada teste."""
    Config.reset()
    yield
    Config.cleanup()

def test_sem_chamar_fixture():
    # reset_config é executado automaticamente
    assert Config.get_setting("debug") is False

Conftest.py para Fixtures Compartilhadas

# tests/conftest.py
import pytest

@pytest.fixture
def cliente():
    """Fixture compartilhada para todos os testes."""
    app = create_app(testing=True)
    with app.test_client() as client:
        yield client

@pytest.fixture
def cabecalhos_auth(cliente):
    """Gera cabeçalhos de autenticação para teste de API."""
    response = cliente.post("/api/login", json={
        "username": "teste",
        "password": "teste"
    })
    token = response.json["token"]
    return {"Authorization": f"Bearer {token}"}

Parametrização

Parametrização Básica

@pytest.mark.parametrize("entrada,esperado", [
    ("ola", "OLA"),
    ("mundo", "MUNDO"),
    ("PyThOn", "PYTHON"),
])
def test_maiusculas(entrada, esperado):
    """Teste executa 3 vezes com diferentes entradas."""
    assert entrada.upper() == esperado

Múltiplos Parâmetros

@pytest.mark.parametrize("a,b,esperado", [
    (2, 3, 5),
    (0, 0, 0),
    (-1, 1, 0),
    (100, 200, 300),
])
def test_somar(a, b, esperado):
    """Testa soma com múltiplas entradas."""
    assert somar(a, b) == esperado

Parametrização com IDs

@pytest.mark.parametrize("entrada,esperado", [
    ("[email protected]", True),
    ("invalido", False),
    ("@sem-dominio.com", False),
], ids=["email-valido", "faltando-arroba", "faltando-dominio"])
def test_validacao_email(entrada, esperado):
    """Testa validação de e-mail com IDs de teste legíveis."""
    assert is_valid_email(entrada) is esperado

Fixtures Parametrizadas

@pytest.fixture(params=["sqlite", "postgresql", "mysql"])
def db(request):
    """Testa com múltiplos bancos de dados."""
    if request.param == "sqlite":
        return Database(":memory:")
    elif request.param == "postgresql":
        return Database("postgresql://localhost/teste")
    elif request.param == "mysql":
        return Database("mysql://localhost/teste")

def test_operacoes_banco_dados(db):
    """Teste executa 3 vezes, uma para cada banco."""
    resultado = db.query("SELECT 1")
    assert resultado is not None

Marcadores e Seleção de Testes

Marcadores Personalizados

# Marcar testes lentos
@pytest.mark.slow
def test_operacao_lenta():
    time.sleep(5)

# Marcar testes de integração
@pytest.mark.integration
def test_integracao_api():
    response = requests.get("https://api.exemplo.com")
    assert response.status_code == 200

# Marcar testes unitários
@pytest.mark.unit
def test_logica_unitaria():
    assert calcular(2, 3) == 5

Executar Testes Específicos

# Executar apenas testes rápidos
pytest -m "not slow"

# Executar apenas testes de integração
pytest -m integration

# Executar testes de integração ou lentos
pytest -m "integration or slow"

# Executar testes marcados como unitários, mas não lentos
pytest -m "unit and not slow"

Configurar Marcadores no pytest.ini

[pytest]
markers =
    slow: marca testes como lentos
    integration: marca testes como testes de integração
    unit: marca testes como testes unitários
    django: marca testes como requerendo Django

Mocking e Patching

Mockando Funções

from unittest.mock import patch, Mock

@patch("meupacote.chamada_api_externa")
def test_com_mock(chamada_mock):
    """Testa com API externa simulada."""
    chamada_mock.return_value = {"status": "sucesso"}

    resultado = minha_funcao()

    chamada_mock.assert_called_once()
    assert resultado["status"] == "sucesso"

Mockando Valores de Retorno

@patch("meupacote.Database.connect")
def test_conexao_banco_dados(connect_mock):
    """Testa com conexão de banco de dados simulada."""
    connect_mock.return_value = MockConnection()

    db = Database()
    db.connect()

    connect_mock.assert_called_once_with("localhost")

Mockando Exceções

@patch("meupacote.chamada_api")
def test_tratamento_erro_api(chamada_mock):
    """Testa tratamento de erro com exceção simulada."""
    chamada_mock.side_effect = ConnectionError("Erro de rede")

    with pytest.raises(ConnectionError):
        chamada_api()

    chamada_mock.assert_called_once()

Mockando Gerenciadores de Contexto

@patch("builtins.open", new_callable=mock_open)
def test_leitura_arquivo(arquivo_mock):
    """Testa leitura de arquivo com open simulado."""
    arquivo_mock.return_value.read.return_value = "conteúdo do arquivo"

    resultado = ler_arquivo("teste.txt")

    arquivo_mock.assert_called_once_with("teste.txt", "r")
    assert resultado == "conteúdo do arquivo"

Usando Autospec

@patch("meupacote.ConexaoBD", autospec=True)
def test_autospec(db_mock):
    """Testa com autospec para detectar mau uso da API."""
    db = db_mock.return_value
    db.query("SELECT * FROM usuarios")

    # Isso falharia se ConexaoBD não tiver método query
    db_mock.assert_called_once()

Mockando Instâncias de Classes

class TestServicoUsuario:
    @patch("meupacote.RepositorioUsuario")
    def test_criar_usuario(self, repo_mock):
        """Testa criação de usuário com repositório simulado."""
        repo_mock.return_value.save.return_value = User(id=1, name="Alice")

        servico = ServicoUsuario(repo_mock.return_value)
        usuario = servico.criar_usuario(name="Alice")

        assert usuario.name == "Alice"
        repo_mock.return_value.save.assert_called_once()

Mockando Propriedades

@pytest.fixture
def config_mock():
    """Cria um mock com uma propriedade."""
    config = Mock()
    type(config).debug = PropertyMock(return_value=True)
    type(config).api_key = PropertyMock(return_value="chave-teste")
    return config

def test_com_config_mock(config_mock):
    """Testa com propriedades de configuração simuladas."""
    assert config_mock.debug is True
    assert config_mock.api_key == "chave-teste"

Testando Código Assíncrono

Testes Assíncronos com pytest-asyncio

import pytest

@pytest.mark.asyncio
async def test_funcao_assincrona():
    """Testa função assíncrona."""
    resultado = await somar_assincrono(2, 3)
    assert resultado == 5

@pytest.mark.asyncio
async def test_assincrono_com_fixture(cliente_assincrono):
    """Testa assíncrono com fixture assíncrona."""
    response = await cliente_assincrono.get("/api/usuarios")
    assert response.status_code == 200

Fixture Assíncrona

@pytest.fixture
async def cliente_assincrono():
    """Fixture assíncrona fornecendo cliente de teste assíncrono."""
    app = create_app()
    async with app.test_client() as client:
        yield client

@pytest.mark.asyncio
async def test_endpoint_api(cliente_assincrono):
    """Testa usando fixture assíncrona."""
    response = await cliente_assincrono.get("/api/dados")
    assert response.status_code == 200

Mockando Funções Assíncronas

@pytest.mark.asyncio
@patch("meupacote.chamada_api_assincrona")
async def test_mock_assincrono(chamada_mock):
    """Testa função assíncrona com mock."""
    chamada_mock.return_value = {"status": "ok"}

    resultado = await minha_funcao_assincrona()

    chamada_mock.assert_awaited_once()
    assert resultado["status"] == "ok"

Testando Exceções

Testando Exceções Esperadas

def test_divisao_por_zero():
    """Testa que dividir por zero levanta ZeroDivisionError."""
    with pytest.raises(ZeroDivisionError):
        dividir(10, 0)

def test_excecao_personalizada():
    """Testa exceção personalizada com mensagem."""
    with pytest.raises(ValueError, match="entrada inválida"):
        validar_entrada("invalido")

Testando Atributos de Exceção

def test_excecao_com_detalhes():
    """Testa exceção com atributos personalizados."""
    with pytest.raises(ErroPersonalizado) as exc_info:
        raise ErroPersonalizado("erro", code=400)

    assert exc_info.value.code == 400
    assert "erro" in str(exc_info.value)

Testando Efeitos Colaterais

Testando Operações de Arquivo

import tempfile
import os

def test_processamento_arquivo():
    """Testa processamento de arquivo com arquivo temporário."""
    with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
        f.write("conteúdo de teste")
        caminho_temp = f.name

    try:
        resultado = processar_arquivo(caminho_temp)
        assert resultado == "processado: conteúdo de teste"
    finally:
        os.unlink(caminho_temp)

Testando com a Fixture tmp_path do pytest

def test_com_tmp_path(tmp_path):
    """Testa usando a fixture de caminho temporário embutida do pytest."""
    arquivo_teste = tmp_path / "teste.txt"
    arquivo_teste.write_text("olá mundo")

    resultado = processar_arquivo(str(arquivo_teste))
    assert resultado == "olá mundo"
    # tmp_path é limpo automaticamente

Testando com Fixture tmpdir

def test_com_tmpdir(tmpdir):
    """Testa usando a fixture tmpdir do pytest."""
    arquivo_teste = tmpdir.join("teste.txt")
    arquivo_teste.write("dados")

    resultado = processar_arquivo(str(arquivo_teste))
    assert resultado == "dados"

Organização de Testes

Estrutura de Diretórios

tests/
├── conftest.py                 # Fixtures compartilhadas
├── __init__.py
├── unit/                       # Testes unitários
│   ├── __init__.py
│   ├── test_models.py
│   ├── test_utils.py
│   └── test_services.py
├── integration/                # Testes de integração
│   ├── __init__.py
│   ├── test_api.py
│   └── test_database.py
└── e2e/                        # Testes ponta a ponta
    ├── __init__.py
    └── test_fluxo_usuario.py

Classes de Teste

class TestServicoUsuario:
    """Agrupa testes relacionados em uma classe."""

    @pytest.fixture(autouse=True)
    def setup(self):
        """Setup é executado antes de cada teste nesta classe."""
        self.servico = ServicoUsuario()

    def test_criar_usuario(self):
        """Testa criação de usuário."""
        usuario = self.servico.criar_usuario("Alice")
        assert usuario.nome == "Alice"

    def test_deletar_usuario(self):
        """Testa exclusão de usuário."""
        usuario = self.servico.criar_usuario("Bob")
        self.servico.deletar_usuario(usuario.id)
        assert self.servico.buscar_usuario(usuario.id) is None

Ferramentas de Qualidade de Código

Configuração do .pylintrc para Testes

[MASTER]
ignore=tests

[MESSAGES CONTROL]
disable=missing-docstring,too-few-public-methods

Configuração do mypy para Testes

[mypy]
allow_redefinition = True
ignore_missing_imports = True

[mypy-tests.*]
ignore_errors = True

Padrões Avançados

Teste de Fábrica (Factory Pattern)

class FabricaUsuario:
    """Fábrica para criar usuários de teste."""
    
    @staticmethod
    def criar(nome="Alice", email="[email protected]"):
        return User(nome=nome, email=email)
    
    @staticmethod
    def admin(nome="Admin", email="[email protected]"):
        user = FabricaUsuario.criar(nome=nome, email=email)
        user.role = "admin"
        return user

def test_usuario_padrao():
    """Teste usando fábrica."""
    usuario = FabricaUsuario.criar()
    assert usuario.nome == "Alice"

def test_usuario_admin():
    """Teste usando fábrica para admin."""
    admin = FabricaUsuario.admin()
    assert admin.role == "admin"

Teste Parametrizado com Dados Externos

import json

@pytest.fixture
def dados_teste():
    """Carrega dados de teste de um arquivo JSON."""
    with open("test_data.json", "r") as f:
        return json.load(f)

def test_com_dados_externos(dados_teste):
    """Teste usando dados carregados externamente."""
    for caso in dados_teste:
        assert processar(caso["entrada"]) == caso["esperado"]

Teste com Skip Condicional

import sys

@pytest.mark.skipif(sys.version_info < (3, 8), reason="Requer Python 3.8 ou superior")
def test_recurso_moderno():
    """Testa recurso disponível apenas em Python 3.8+."""
    # Código que usa recursos modernos

Plugins pytest Recomendados

  • pytest-cov: Medição de cobertura de código
  • pytest-xdist: Execução paralela de testes
  • pytest-mock: Mocking simplificado
  • pytest-asyncio: Suporte a testes assíncronos
  • pytest-timeout: Timeout para testes
  • pytest-randomly: Ordem aleatória de execução

Integração Contínua

Exemplo de Configuração CI/CD (GitHub Actions)

name: Executar Testes

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    - name: Configurar Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.9'
    - name: Instalar dependências
      run: |
        pip install -r requirements.txt
        pip install pytest pytest-cov
    - name: Executar testes com cobertura
      run: |
        pytest tests/ --cov=meupacote --cov-report=xml --cov-report=html
    - name: Enviar cobertura para Codecov
      uses: codecov/codecov-action@v1

Boas Práticas

  1. Escreva testes primeiro: Siga TDD consistentemente
  2. Mantenha testes focados: Cada teste deve testar uma única coisa
  3. Use fixtures para setup: Evite duplicação em setup/teardown
  4. Prefira mocks a efeitos colaterais: Isole dependências externas
  5. Use parametrização: Reduza código repetitivo
  6. Mantenha testes legíveis: Nomes descritivos e documentação
  7. Execute testes com frequência: Integre ao seu fluxo de trabalho
  8. Monitore a cobertura: Alvo de 80%+, caminhos críticos em 100%

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Habilidades Relacionadas