Great Expectations com Python: Testes de Qualidade de Dados

Aprenda a usar Great Expectations com Python para testar qualidade de dados em CSV, Pandas e pipelines, com exemplos práticos para projetos no Brasil.

9 min de leitura Equipe Python Brasil

Qualidade de dados é uma das diferenças mais claras entre um script que roda uma vez e um projeto Python pronto para trabalho real. Em uma planilha pequena, talvez você perceba manualmente que uma coluna veio vazia, que uma data está no formato errado ou que um valor de venda ficou negativo. Em um pipeline recorrente, esse tipo de erro passa silenciosamente para dashboards, modelos, relatórios financeiros, automações de CRM e decisões de negócio.

O problema não é apenas técnico. Quem busca vaga de análise, engenharia de dados, QA de dados ou automação com Python precisa mostrar que sabe transformar dados em confiança. Um projeto que só lê CSV e gera gráfico é útil; um projeto que valida contrato de entrada, bloqueia dados suspeitos e deixa evidência do que foi testado comunica maturidade profissional.

O Great Expectations é uma das ferramentas mais conhecidas para esse trabalho. Ele permite declarar expectativas sobre dados: colunas obrigatórias, tipos, faixas, valores permitidos, unicidade, ausência de nulos, formatos de texto e regras customizadas. Em vez de espalhar if solto pelo pipeline, você cria uma suíte de validações que pode rodar localmente, no CI ou antes de carregar dados em produção.

Neste guia, vamos usar Great Expectations com Pandas para validar uma base simples, conectar o fluxo com pytest, comparar com Pandera e mostrar como transformar o resultado em um projeto de portfólio voltado ao mercado brasileiro.

Quando usar Great Expectations

Use Great Expectations quando os dados têm um contrato que precisa ser verificado de forma repetível. Alguns exemplos comuns:

CenárioExpectativas úteis
CSV de vendaspedido único, valor positivo, data válida, status permitido
Base de clientese-mail não nulo, CPF com formato esperado, cidade preenchida
Pipeline de vagastítulo obrigatório, empresa obrigatória, URL única, salário coerente
Dados públicoscódigo IBGE válido, UF permitida, ano dentro do intervalo esperado
Analyticseventos com nome conhecido, usuário anônimo presente, timestamp válido

Se você só precisa validar um DataFrame pequeno dentro do próprio código Python, Pandera pode ser mais direto. Se quer documentação de qualidade de dados, suítes reutilizáveis, relatórios de validação e uma ferramenta conhecida por times de dados, Great Expectations faz sentido.

O ponto principal é não tratar qualidade como etapa manual. Dados mudam. APIs quebram. Exportações de sistema ganham coluna nova. Alguém altera separador decimal. Uma validação automatizada transforma esse risco em sinal visível.

Instalando o ambiente

Crie um projeto simples:

uv init qualidade-dados-python
cd qualidade-dados-python
uv add great-expectations pandas pytest

Com pip, o equivalente seria:

python -m venv .venv
source .venv/bin/activate
pip install great-expectations pandas pytest

Monte esta estrutura:

qualidade-dados-python/
  data/
    vendas.csv
  src/
    validar_vendas.py
  tests/
    test_qualidade_vendas.py

O arquivo data/vendas.csv pode começar assim:

pedido_id,cliente_email,uf,valor,status,data_pedido
1001,[email protected],SP,129.90,pago,2026-05-20
1002,[email protected],MG,89.50,enviado,2026-05-21
1003,[email protected],PE,240.00,cancelado,2026-05-22

Essa base parece simples, mas já tem regras de negócio: pedido_id não deve repetir, uf precisa estar em uma lista conhecida, valor não pode ser negativo e status deve ficar dentro dos valores aceitos.

Validando um DataFrame com expectativas

Crie src/validar_vendas.py:

from pathlib import Path

import great_expectations as gx
import pandas as pd


UFS_BRASIL = [
    "AC", "AL", "AP", "AM", "BA", "CE", "DF", "ES", "GO",
    "MA", "MT", "MS", "MG", "PA", "PB", "PR", "PE", "PI",
    "RJ", "RN", "RS", "RO", "RR", "SC", "SP", "SE", "TO",
]


def carregar_vendas(caminho: str | Path) -> pd.DataFrame:
    return pd.read_csv(caminho, parse_dates=["data_pedido"])


def validar_vendas(df: pd.DataFrame) -> bool:
    batch = gx.from_pandas(df)

    batch.expect_table_columns_to_match_ordered_list(
        ["pedido_id", "cliente_email", "uf", "valor", "status", "data_pedido"]
    )
    batch.expect_column_values_to_be_unique("pedido_id")
    batch.expect_column_values_to_not_be_null("cliente_email")
    batch.expect_column_values_to_match_regex(
        "cliente_email",
        r"^[^@\s]+@[^@\s]+\.[^@\s]+$",
    )
    batch.expect_column_values_to_be_in_set("uf", UFS_BRASIL)
    batch.expect_column_values_to_be_between("valor", min_value=0.01)
    batch.expect_column_values_to_be_in_set("status", ["pago", "enviado", "cancelado"])
    batch.expect_column_values_to_not_be_null("data_pedido")

    resultado = batch.validate()
    return bool(resultado.success)


if __name__ == "__main__":
    vendas = carregar_vendas("data/vendas.csv")
    if not validar_vendas(vendas):
        raise SystemExit("Falha na validação de qualidade de dados")
    print("Dados de vendas validados com sucesso")

O exemplo usa gx.from_pandas() para ficar didático. Em projetos maiores, você pode evoluir para Data Context, fontes de dados configuradas e checkpoints. Para começar, porém, o valor já aparece: as regras ficam explícitas e qualquer pessoa consegue entender o contrato da base.

O que cada expectativa protege

As expectativas não devem ser escolhidas por volume, e sim por risco. No exemplo acima:

  • expect_table_columns_to_match_ordered_list protege o contrato de entrada. Se a exportação mudar nome ou ordem de coluna, o pipeline para cedo.
  • expect_column_values_to_be_unique evita duplicar pedidos em métricas de receita.
  • expect_column_values_to_not_be_null impede registros sem e-mail quando a comunicação com cliente depende desse campo.
  • expect_column_values_to_match_regex captura e-mails claramente inválidos.
  • expect_column_values_to_be_in_set em uf impede valores como Sao Paulo, SP ou XX.
  • expect_column_values_to_be_between em valor evita vendas negativas ou zeradas entrando em indicadores.
  • expect_column_values_to_be_in_set em status impede que um status novo passe sem decisão explícita.

Essa leitura é importante para carreira. Em entrevistas, não basta dizer “usei Great Expectations”. Melhor é explicar qual risco cada validação reduz e qual decisão o time deve tomar quando ela falha.

Integrando com pytest

Para um projeto de portfólio, uma boa prática é colocar a validação no mesmo fluxo de testes. Crie tests/test_qualidade_vendas.py:

import pandas as pd

from src.validar_vendas import validar_vendas


def test_vendas_validas_passam_na_validacao():
    df = pd.DataFrame(
        {
            "pedido_id": [1001, 1002],
            "cliente_email": ["[email protected]", "[email protected]"],
            "uf": ["SP", "MG"],
            "valor": [129.90, 89.50],
            "status": ["pago", "enviado"],
            "data_pedido": pd.to_datetime(["2026-05-20", "2026-05-21"]),
        }
    )

    assert validar_vendas(df) is True


def test_venda_com_valor_negativo_falha():
    df = pd.DataFrame(
        {
            "pedido_id": [1001],
            "cliente_email": ["[email protected]"],
            "uf": ["SP"],
            "valor": [-10.0],
            "status": ["pago"],
            "data_pedido": pd.to_datetime(["2026-05-20"]),
        }
    )

    assert validar_vendas(df) is False

Agora rode:

pytest

Esse teste não substitui testes unitários de transformação, mas adiciona um nível de contrato para dados. Em um pipeline real, você pode rodar testes de função, validações de schema e validações de amostra antes de publicar a tabela final.

Usando em um pipeline simples

Um fluxo mínimo de ETL pode ficar assim:

from pathlib import Path

from src.validar_vendas import carregar_vendas, validar_vendas


def transformar(caminho_entrada: Path, caminho_saida: Path) -> None:
    vendas = carregar_vendas(caminho_entrada)

    if not validar_vendas(vendas):
        raise ValueError("Base de vendas rejeitada pela validação")

    resumo = (
        vendas.groupby("uf", as_index=False)
        .agg(receita=("valor", "sum"), pedidos=("pedido_id", "count"))
        .sort_values("receita", ascending=False)
    )

    resumo.to_csv(caminho_saida, index=False)

O detalhe é a ordem: valide antes de transformar. Se dados ruins entram no agrupamento, você pode gerar relatório bonito com número errado. Em dados, erro silencioso é pior do que falha explícita.

Também vale registrar falhas em um arquivo separado. Por exemplo, se o pipeline recebe CSV de fornecedores, você pode salvar o lote rejeitado em data/rejeitados/ com timestamp e motivo. Isso ajuda a conversar com a área de negócio sem perder evidência.

Great Expectations ou Pandera?

A comparação mais útil é por contexto:

FerramentaMelhor uso
Panderavalidação tipada dentro do código Python, especialmente com Pandas
Great Expectationssuítes de expectativas, documentação de qualidade e validações compartilháveis
pytest puroregras muito específicas de função ou transformação
Pydanticvalidação de objetos, configurações, APIs e dados de entrada estruturados

Em um projeto real, elas podem conviver. Você pode usar Pydantic para validar payloads de API, Pandera para validar DataFrame intermediário, Great Expectations para validar contrato de tabela e pytest para garantir comportamento de funções.

Evite a armadilha de instalar ferramenta demais sem clareza. Para um primeiro projeto, escolha uma base de dados, escreva 8 a 12 expectativas relevantes, rode no CI e documente o que acontece quando a validação falha.

Exemplo com dados públicos brasileiros

Um bom projeto para currículo é validar um fluxo com dados públicos brasileiros. Você pode usar uma base de municípios, indicadores educacionais, dados de saúde, despesas públicas ou séries temporais. O README poderia explicar:

  • fonte dos dados;
  • frequência esperada de atualização;
  • colunas obrigatórias;
  • expectativas de qualidade;
  • exemplos de falha;
  • comando para rodar validação;
  • saída gerada após aprovação.

Imagine uma base mensal de municípios:

codigo_ibge,municipio,uf,ano,mes,valor
3550308,São Paulo,SP,2026,5,1024.50
2611606,Recife,PE,2026,5,310.40

As expectativas poderiam validar que codigo_ibge tem sete dígitos, uf está no conjunto de estados, ano está entre 2000 e 2030, mes fica entre 1 e 12 e valor não é negativo. Esse tipo de projeto conversa bem com APIs públicas brasileiras com Python, ETL com Python, DuckDB e GeoPandas.

Para deixar o portfólio mais forte, inclua um caso de falha proposital. Um diretório examples/dados_invalidos.csv mostra que você pensou no caminho ruim, não apenas no demo perfeito.

Rodando no CI

Em GitHub Actions ou Gitea Actions, a validação pode rodar junto com os testes:

name: quality

on:
  push:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5
      - run: uv sync
      - run: uv run pytest
      - run: uv run python src/validar_vendas.py

Esse fluxo evita que uma mudança de dados ou código seja aceita sem passar pelo contrato mínimo. Em times maiores, você pode separar validações rápidas de PR e validações completas de agenda diária, porque bases grandes podem demorar.

Cuidados em produção

Algumas práticas evitam frustração:

  • Comece por expectativas críticas. Não tente modelar 100% da realidade no primeiro dia.
  • Defina severidade. Uma coluna opcional ausente pode gerar alerta; CPF inválido em base de cobrança talvez bloqueie carga.
  • Versione expectativas junto com o código. Mudança de regra deve passar por revisão.
  • Não valide apenas schema. Distribuição, faixas e cardinalidade também importam.
  • Separe dado rejeitado de dado aprovado. Isso facilita auditoria.
  • Monitore tendência de falhas. Se a mesma expectativa quebra toda semana, o contrato ou a fonte precisam ser revistos.

Também cuidado com dados pessoais. Se o projeto usa e-mail, CPF, telefone ou endereço, trabalhe com dados fictícios ou anonimizados. Para portfólio público, mostre estrutura e validação sem expor informação real.

Como apresentar no portfólio

Um bom README para esse tipo de projeto pode ter esta estrutura:

# Validação de qualidade de dados com Python

## Problema
Base de vendas mensal pode chegar com duplicidade, valores inválidos e estados incorretos.

## Solução
Pipeline em Python com Pandas, Great Expectations e pytest.

## Regras de qualidade
- pedido_id único
- valor maior que zero
- UF dentro da lista oficial
- status dentro do contrato
- e-mail com formato válido

## Como rodar
uv run pytest
uv run python src/validar_vendas.py

## Evidência
Inclui exemplo de base válida, base inválida e saída de validação.

Esse formato é mais convincente do que apenas listar tecnologias. Ele mostra problema, decisão, validação e evidência. Para vagas de dados, QA, engenharia analítica e automação, essa clareza pesa bastante.

Próximos passos

Depois do primeiro fluxo funcionando, evolua por etapas:

  1. Adicione validações para dados públicos brasileiros.
  2. Gere documentação HTML das expectativas.
  3. Rode validação em CI.
  4. Salve lotes rejeitados com timestamp.
  5. Publique um dashboard simples com apenas dados aprovados.
  6. Compare Great Expectations com Pandera no README.

Se o volume crescer, combine a validação com Parquet, DuckDB, Polars ou banco analítico. Quando a rotina precisar de agendamento, retries e histórico por etapa, conecte a validação a um orquestrador como Airflow com Python. Se o gargalo for ingestão em alta concorrência, uma arquitetura mista pode usar Go para coletar eventos e Python para validação, análise e ciência de dados.

Great Expectations não resolve qualidade de dados sozinho. A ferramenta só funciona quando você transforma conhecimento de negócio em contrato verificável. Esse é o ganho real: menos confiança cega em planilha, mais evidência automatizada antes de tomar decisão.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português