---
title: "pytest parametrize: testes parametrizados com exemplos"
url: "https://python.dev.br/blog/pytest-parametrize-testes-parametrizados-python/"
markdown_url: "https://python.dev.br/blog/pytest-parametrize-testes-parametrizados-python.MD"
description: "Aprenda pytest.mark.parametrize com testes de CEP, IDs legíveis, exceções, fixtures e indirect. Execute um projeto completo e evite erros comuns."
date: "2026-10-06"
author: "Equipe Python Dev BR"
---

# pytest parametrize: testes parametrizados com exemplos

Aprenda pytest.mark.parametrize com testes de CEP, IDs legíveis, exceções, fixtures e indirect. Execute um projeto completo e evite erros comuns.


**Use `@pytest.mark.parametrize` para executar a mesma regra de teste com várias entradas e resultados esperados, criando um caso independente para cada combinação.** A lista de cenários fica explícita, as falhas aparecem separadamente e você não precisa copiar funções quase iguais. Use `pytest.param(..., id="nome")` para identificar cada cenário e reserve `indirect` para valores que precisam ser transformados por uma fixture.

Neste tutorial, vamos construir testes de normalização de CEP: entrada com ou sem hífen, espaços, zero à esquerda e formatos inválidos. O exemplo é útil em formulários e importações de cadastros brasileiros, mas **validar o formato não confirma que o CEP existe**. Não faremos consultas ao ViaCEP nem acessaremos dados pessoais.

Se você ainda não conhece descoberta de testes e fixtures, comece pelo [guia de testes com pytest](/guias/testes-com-pytest/). Aqui o foco é escolher e organizar os cenários, entender a coleta e evitar armadilhas da parametrização.

## Parametrize, loop ou fixture: qual escolher?

| Necessidade | Recurso indicado | Motivo |
| --- | --- | --- |
| Mesma regra, várias entradas e saídas esperadas | `@pytest.mark.parametrize` | Cada cenário vira um teste independente |
| Preparar arquivo temporário para o teste | Fixture, como `tmp_path` | Separa preparação da asserção |
| Executar vários testes com duas configurações | Fixture com `params` | Compartilha a matriz de configurações |
| Transformar um parâmetro em recurso | `indirect` + fixture | Preparação ocorre durante a execução do teste |
| Verificar uma coleção inteira como uma unidade | Um teste com loop pode bastar | A coleção é o objeto do comportamento verificado |

Um loop não é proibido. A diferença é o diagnóstico: se a segunda asserção falhar dentro de uma função, a terceira geralmente não será executada. Com parametrização, o pytest continua nos demais casos, salvo opções de interrupção como `-x` ou `--maxfail=1`.

## Projeto completo: normalizar CEP e testar os formatos

Os exemplos usam Python 3.10 ou superior e pytest. Crie uma pasta vazia, um ambiente virtual e instale a dependência:

```bash
python -m venv .venv
```

Ative o ambiente no Linux ou macOS:

```bash
source .venv/bin/activate
```

No PowerShell do Windows, o comando correspondente é:

```powershell
.\.venv\Scripts\Activate.ps1
```

Depois da ativação:

```bash
python -m pip install pytest
```

Se precisar de ajuda, consulte [como criar um ambiente virtual](/guias/criando-virtual-environment/). A estrutura inicial será:

```text
projeto/
├── cep.py
└── test_cep.py
```

### 1. Implemente uma regra pequena e explícita

Salve como `cep.py`:

```python
import re


def normalizar_cep(valor: str) -> str:
    if not isinstance(valor, str):
        raise TypeError("CEP deve ser texto")

    texto = valor.strip()
    if re.fullmatch(r"[0-9]{5}-?[0-9]{3}", texto) is None:
        raise ValueError("Formato de CEP inválido")

    return texto.replace("-", "")
```

O contrato é intencionalmente restrito: oito dígitos ASCII, hífen opcional na posição correta e espaços externos permitidos. Espaços internos e pontuação extra são rejeitados. Usamos `[0-9]` em vez de `\d`, pois `\d` também reconhece outros dígitos Unicode.

CEP é identificador, não quantidade. Mantenha-o como texto para preservar o zero inicial de `01001000`. Não converta para `int` em uma planilha ou no carregamento de um CSV. Veja também [validação de CPF, CNPJ e CEP](/blog/validar-cpf-cnpj-cep-python-brasilapi-viacep/) para distinguir validação local de consulta a uma API.

### 2. Defina entradas e resultados esperados

Salve como `test_cep.py`:

```python
import pytest

from cep import normalizar_cep


@pytest.mark.parametrize(
    "entrada, esperado",
    [
        pytest.param("01001000", "01001000", id="sem-hifen"),
        pytest.param("01001-000", "01001000", id="com-hifen"),
        pytest.param(" 01001-000 ", "01001000", id="espacos-externos"),
        pytest.param("30140-071", "30140071", id="outro-formato-valido"),
    ],
)
def test_normalizar_cep_valido(entrada, esperado):
    assert normalizar_cep(entrada) == esperado


@pytest.mark.parametrize(
    "entrada",
    [
        pytest.param("", id="vazio"),
        pytest.param("0100100", id="sete-digitos"),
        pytest.param("010010000", id="nove-digitos"),
        pytest.param("0100-1000", id="hifen-fora-de-posicao"),
        pytest.param("01001 000", id="espaco-interno"),
        pytest.param("ABCDE-000", id="letras"),
        pytest.param("０１００１０００", id="digitos-nao-ascii"),
    ],
)
def test_rejeitar_formato_invalido(entrada):
    with pytest.raises(ValueError, match="Formato de CEP inválido"):
        normalizar_cep(entrada)


@pytest.mark.parametrize(
    "entrada",
    [
        pytest.param(None, id="nulo"),
        pytest.param(1001000, id="inteiro"),
    ],
)
def test_rejeitar_tipo_invalido(entrada):
    with pytest.raises(TypeError, match="CEP deve ser texto"):
        normalizar_cep(entrada)
```

Execute na raiz do projeto:

```bash
python -m pytest -q
```

São **13 casos**: quatro válidos, sete formatos inválidos e dois tipos inválidos. Cada item da lista gera uma execução da função correspondente.

A ordem em `"entrada, esperado"` corresponde à ordem dos valores de cada `pytest.param`. A função precisa receber argumentos com esses mesmos nomes. Para um só argumento, passe diretamente os valores; não é necessário embrulhar cada string em uma tupla.

Os resultados esperados são literais definidos a partir do contrato. Evite calculá-los chamando a própria função testada: comparar uma função com ela mesma pode aprovar o mesmo erro duas vezes.

## IDs: encontre o caso que falhou sem ler toda a matriz

Sem IDs explícitos, o pytest tenta gerar nomes a partir dos parâmetros. Isso funciona para valores simples, mas pode produzir identificadores pouco úteis para objetos complexos.

`pytest.param` permite colocar o nome ao lado dos dados, o que facilita manter uma matriz grande. Outra opção é `ids=["caso-a", "caso-b"]` no decorator; a lista deve acompanhar a quantidade e a ordem dos cenários.

Confira os testes coletados sem executá-los:

```bash
python -m pytest --collect-only -q
```

A lista inclui um identificador como:

```text
test_cep.py::test_normalizar_cep_valido[com-hifen]
```

Você pode executar apenas esse caso, usando aspas para proteger os colchetes no shell:

```bash
python -m pytest 'test_cep.py::test_normalizar_cep_valido[com-hifen]' -v
```

Também pode selecionar por expressão:

```bash
python -m pytest -k 'com-hifen' -v
```

`-k` seleciona por nomes e palavras-chave dos testes; não avalia o conteúdo dos parâmetros. Prefira o identificador completo quando precisar de uma seleção exata. Não coloque tokens, e-mails reais ou dados de clientes nos IDs: eles podem aparecer em logs e relatórios do CI.

## Exceções: separe sucesso de entrada inválida

No projeto acima, os cenários válidos e inválidos ficam em funções diferentes. Isso mantém a leitura simples: uma função compara o resultado, outra exige uma exceção.

`pytest.raises(ValueError)` confirma que o bloco lançou o tipo esperado. O argumento `match` verifica a mensagem com uma **expressão regular**, não com uma comparação literal. Se a mensagem tiver caracteres especiais como parênteses ou pontos e você quiser uma busca literal, escape o padrão com `re.escape()`.

Não use apenas `pytest.raises(Exception)` por comodidade. Um erro inesperado de implementação poderia ser aceito como se fosse a rejeição correta da entrada. Para revisar os fundamentos, consulte [tratamento de erros em Python](/blog/tratamento-de-erros-python/).

## Combinar fixtures com argumentos parametrizados

Uma fixture comum pode coexistir com `parametrize`. Por exemplo, `tmp_path` prepara um diretório temporário separado para cada teste. Acrescente ao mesmo `test_cep.py`:

```python
@pytest.mark.parametrize(
    "conteudo, esperado",
    [
        pytest.param("01001-000\n", "01001000", id="arquivo-com-hifen"),
        pytest.param("30140071\n", "30140071", id="arquivo-sem-hifen"),
    ],
)
def test_ler_cep_de_arquivo(tmp_path, conteudo, esperado):
    arquivo = tmp_path / "cep.txt"
    arquivo.write_text(conteudo, encoding="utf-8")

    entrada = arquivo.read_text(encoding="utf-8")
    assert normalizar_cep(entrada) == esperado
```

`conteudo` e `esperado` vêm da parametrização; `tmp_path` vem do pytest. Não inclua `tmp_path` na lista de nomes do decorator se deseja receber a fixture normalmente.

O teste usa somente dados de demonstração e arquivos temporários. Para pipelines maiores, isso evita depender de uma pasta pessoal ou sobrescrever um arquivo de trabalho. O tutorial de [pathlib](/blog/python-pathlib-manipulacao-caminhos-arquivos/) aprofunda as operações com caminhos.

## Quando usar indirect

Use `indirect` quando o parâmetro descreve **como preparar um recurso**, e não quando ele já é o valor final que o teste precisa. Acrescente este exemplo ao arquivo de testes:

```python
@pytest.fixture
def arquivo_cep(request, tmp_path):
    arquivo = tmp_path / "entrada.txt"
    arquivo.write_text(request.param, encoding="utf-8")
    return arquivo


@pytest.mark.parametrize(
    "arquivo_cep, esperado",
    [
        pytest.param("01001-000\n", "01001000", id="arquivo-sp"),
        pytest.param("30140-071\n", "30140071", id="arquivo-bh"),
    ],
    indirect=["arquivo_cep"],
)
def test_cep_com_preparacao_indireta(arquivo_cep, esperado):
    entrada = arquivo_cep.read_text(encoding="utf-8")
    assert normalizar_cep(entrada) == esperado
```

O fluxo é:

1. O pytest coleta o cenário com uma string de conteúdo.
2. Na preparação do teste, envia essa string à fixture como `request.param`.
3. A fixture cria o arquivo e devolve um objeto `Path`.
4. O teste recebe o `Path` em `arquivo_cep` e a string final em `esperado`.

Usamos `indirect=["arquivo_cep"]`, não `indirect=True`, para transformar apenas o argumento que precisa da fixture. Com `True`, todos os argumentos parametrizados seriam tratados como nomes de fixtures — inclusive `esperado`.

Se o teste só precisa de uma string, não use indireção. Ela é útil para clientes, arquivos e recursos com preparação ou limpeza; para uma soma ou validação pura, deixa o exemplo mais complicado sem benefício.

## Fixture com params: reutilize configurações entre testes

Quando vários testes devem rodar com as mesmas configurações, a parametrização pode morar na fixture. Este exemplo independente pode ser salvo como `test_separadores.py`:

```python
import csv
import io

import pytest


@pytest.fixture(params=[",", ";"], ids=["virgula", "ponto-e-virgula"])
def separador(request):
    return request.param


def test_csv_preserva_zero_inicial(separador):
    conteudo = f"cep{separador}cidade\n01001000{separador}São Paulo\n"
    linhas = list(csv.DictReader(io.StringIO(conteudo), delimiter=separador))
    assert linhas[0]["cep"] == "01001000"


def test_csv_preserva_nome_da_cidade(separador):
    conteudo = f"cep{separador}cidade\n01001000{separador}São Paulo\n"
    linhas = list(csv.DictReader(io.StringIO(conteudo), delimiter=separador))
    assert linhas[0]["cidade"] == "São Paulo"
```

São quatro casos: duas funções vezes dois separadores. A lógica de CSV trata campos como texto nesse exemplo, sem conversão numérica. Para importar dados de planilhas e sistemas distintos, veja [leitura e escrita de CSV em Python](/blog/python-csv-leitura-escrita-arquivos/).

## Decorators empilhados geram produto cartesiano

Empilhar parametrizações é útil quando **todas as combinações** importam. Por exemplo, duas opções de um argumento e três de outro geram seis casos. Não funciona como um `zip` que combina somente itens de mesma posição.

Se cada entrada tem um resultado específico, coloque ambos na mesma tupla, como fizemos com `entrada, esperado`. Separar entradas e resultados em decorators diferentes criaria combinações incorretas.

Também cuide do tamanho da suíte: cinco dimensões de dez opções geram 100 mil casos. Teste todas as combinações quando houver razão técnica, não apenas porque o decorator permite. Matrizes pequenas de limites e regressões costumam ser mais fáceis de manter.

## Erros comuns em testes parametrizados

- **Nome divergente:** o decorator declara `entrada`, mas a função recebe `valor`. Corrija os nomes; o problema acontece na coleta, antes da asserção.
- **Quantidade de valores incorreta:** três nomes exigem três valores em cada cenário.
- **Argumento chamado `request`:** esse nome é reservado no contexto da parametrização do pytest. Escolha um nome como `requisicao` para seus dados.
- **Dados mutáveis compartilhados:** listas e dicionários são passados como estão, sem cópia automática. Se vários casos referenciam o mesmo objeto e o código o modifica, um caso pode contaminar o seguinte. Crie objetos novos ou prepare cópias adequadas em fixtures.
- **Lista vazia de cenários:** com a configuração padrão, uma parametrização vazia resulta em um teste pulado. Se os dados deveriam existir, valide a geração da matriz e considere configurar `empty_parameter_set_mark` para falhar na coleta.
- **Misturar rede com validação local:** um teste de formato não precisa chamar ViaCEP. Teste o cliente HTTP separadamente com transporte controlado e mantenha testes de integração identificados.
- **Usar `xfail` para esconder um defeito:** essa marca comunica uma falha esperada conhecida. Se usada, documente o motivo e considere `strict=True` para sinalizar quando o teste passar inesperadamente.

## Checklist rápido

Antes de enviar a mudança ao repositório:

- A regra testada tem um contrato explícito?
- Há cenários comuns, limites e entradas inválidas relevantes?
- Os resultados esperados são independentes da implementação?
- Os IDs explicam o cenário sem expor dados pessoais?
- Fixtures preparam recursos e as funções verificam comportamento?
- A matriz evita combinações redundantes ou impossíveis?
- `--collect-only` mostra a quantidade esperada?
- Todos os testes passam com `python -m pytest -q`?

Com todos os arquivos e acréscimos deste tutorial, a suíte tem **21 casos**: 13 iniciais, dois com `tmp_path`, dois com `indirect` e quatro de CSV. Isso oferece uma checagem simples de que você montou os exemplos por completo.

## Conclusão

`pytest.mark.parametrize` torna uma regra verificável em muitos cenários sem duplicar funções. Comece por entradas e resultados explícitos, nomeie os casos e teste as falhas com exceções específicas. Adicione fixtures quando houver recursos a preparar; use `indirect` apenas quando a transformação fizer parte dessa preparação.

Para um projeto de portfólio, explique no README quais falhas sua matriz detecta e como executar a suíte. Essa demonstração é mais útil do que uma grande quantidade de testes sem propósito — especialmente ao discutir qualidade de código em uma entrevista para [vagas Python](/vagas/).

## Referências

- [Documentação do pytest: parametrização](https://docs.pytest.org/en/stable/how-to/parametrize.html)
- [Documentação do pytest: exemplos de parametrização e indirect](https://docs.pytest.org/en/stable/example/parametrize.html)
- [Documentação do pytest: fixtures parametrizadas](https://docs.pytest.org/en/stable/how-to/fixtures.html#parametrizing-fixtures)
- [Documentação do pytest: asserções e exceções](https://docs.pytest.org/en/stable/how-to/assert.html)
