Como usar o PyTest no Python para Testar seu Código

Aprenda a usar o PyTest no Python com fixtures, parametrize e Mock. Automatize seus testes unitários e garanta a qualidade do código como um dev profissional.

Resposta rápida: O PyTest encontra e roda os testes sozinho pela convenção de nomes — arquivos test_*.py e funções test_* — usando o assert puro do Python, sem classe nem boilerplate. Sobre essa base vêm os três recursos que evitam repetição: fixtures (preparo reaproveitável), @pytest.mark.parametrize (vários cenários numa função só) e Mock (simular API e dependência externa).

Introdução

Você já fez uma alteração no seu código Python e, sem querer, quebrou alguma funcionalidade que estava funcionando perfeitamente? Esse é um problema clássico no dia a dia de qualquer desenvolvedor, e é exatamente para isso que o PyTest existe.

O PyTest é uma biblioteca do Python especializada em testes unitários, ou seja, ela permite automatizar a verificação de que cada parte do seu sistema continua funcionando como esperado, mesmo depois de qualquer mudança no código. Em vez de você abrir o terminal, criar prints e checar os resultados um por um, o PyTest faz tudo isso com um único comando e te diz exatamente o que passou e o que falhou.

Neste artigo, você vai aprender na prática as principais funcionalidades do PyTest, do teste mais simples com assert até o uso de fixtures, parametrize e Mock para simular dependências externas. O exemplo que vamos usar ao longo de tudo é um sistema de carrinho de compras, parecido com o que você encontra em qualquer e-commerce.

Para fazer o download do(s) arquivo(s) utilizados na aula, preencha com o seu e-mail:

Não vamos te encaminhar nenhum tipo de SPAM! A Hashtag Treinamentos é uma empresa preocupada com a proteção de seus dados e realiza o tratamento de acordo com a Lei Geral de Proteção de Dados (Lei n. 13.709/18). Qualquer dúvida, nos contate.

Por que testar o código Python é essencial

Imagina que você trabalha num time de desenvolvimento e precisa atualizar a lógica de desconto do carrinho de compras do sistema. Você faz a alteração, testa manualmente, parece tudo certo. Mas, alguns dias depois, o sistema começa a cobrar valores errados dos clientes. O que aconteceu? Sua alteração quebrou uma funcionalidade que você não testou.

Esse tipo de problema tem nome: regressão. E ele é muito mais comum do que parece, especialmente em sistemas grandes com muitas funções interligadas. Por isso, testar o código de forma sistemática, a cada alteração, não é um capricho, é uma necessidade.

Para entender melhor os diferentes tipos de teste que existem, vale a leitura do artigo Testes automatizados: guia prático para aplicar no código aqui do blog da Hashtag, que explica a diferença entre testes unitários, de integração e E2E.

Testes manuais vs PyTest: por que mudar?

Quando você não usa uma ferramenta de testes, o processo costuma ser assim: você cria variáveis de teste, roda o código, olha o print no terminal e decide se está certo ou errado. Funciona para projetos pequenos, mas escala muito mal.

Pensa no seguinte: seu sistema tem 30 funções diferentes. A cada alteração, você precisa testar todas elas manualmente? E se você esquecer uma? E se o valor de saída mudar e você não perceber porque estava olhando para outra coisa? Os testes manuais dependem 100% da sua atenção em cada verificação, o que na prática significa que erros passam despercebidos o tempo todo.

O PyTest resolve isso de forma direta: você escreve a verificação uma vez, e ela roda automaticamente toda vez que você precisar, sem esforço adicional.

O que o PyTest automatiza na prática

Com o PyTest, você roda todos os seus testes com um único comando no terminal e recebe um relatório completo: quantos passaram, quantos falharam, e em qual função exatamente o problema está. Empresas como Mercado Livre e iFood integram esse processo em pipelines de CI/CD, rodando centenas de testes automaticamente a cada novo código enviado ao repositório.

O resultado é que os bugs são identificados antes de chegarem ao usuário final, e o time consegue corrigir problemas muito mais rápido e com muito mais confiança.

Como instalar e configurar o PyTest

Instalar o PyTest é simples. Com o Python já instalado na sua máquina, abra o terminal e rode:

Codigo
pip install pytest

Se você estiver dentro de um ambiente virtual, o comando é o mesmo. Para confirmar que a instalação funcionou, rode:

Codigo
pytest --version

Toda a documentação oficial está disponível em docs.pytest.org, que é a referência principal sempre que você quiser explorar funcionalidades mais avançadas.

Convenção de nomes: arquivos e funções test_

Para o PyTest funcionar corretamente, existe uma convenção de nomes que você precisa seguir. Ela não é opcional, é o que permite ao PyTest descobrir e executar seus testes automaticamente.

A regra é simples:

  • Arquivos de teste precisam começar com test_ (exemplo: test_carrinho.py)
  • Funções de teste também precisam começar com test_ (exemplo: test_calcular_total_sem_desconto)

Quando você rodar o comando pytest no terminal, ele vai varrer todos os arquivos que seguem essa convenção e executar todas as funções que seguem o mesmo padrão. O nome da função é importante porque é ele que vai aparecer no relatório, então escolha nomes descritivos que deixem claro o que está sendo testado.

Escreva seu primeiro teste com assert

O coração de qualquer teste no PyTest é o assert. Você provavelmente já usou o assert em Python para depurar código, mas dentro do PyTest ele ganha um papel central. Se você ainda não conhece bem essa instrução, o artigo Assert em Python, Como Depurar Seus Códigos? aqui do blog explica o funcionamento em detalhes.

A lógica é direta: você escreve uma verificação, e se ela for verdadeira o teste passa. Se for falsa, o teste falha e o PyTest te mostra exatamente o valor que veio vs. o valor que era esperado.

Usando o nosso carrinho de compras como exemplo, veja como fica um teste básico:

Python
from carrinho import Produto, CarrinhoDeCompras

def test_calcular_total_sem_desconto():
    carrinho = CarrinhoDeCompras()
    carrinho.adicionar_produto(Produto('Notebook', 3000))
    carrinho.adicionar_produto(Produto('Teclado', 500))
    total = carrinho.calcular_total()
    assert total == 3500

Quando você rodar pytest no terminal, o PyTest vai executar essa função e verificar se o total retornado é realmente 3500. Se a sua função calcular_total() estiver funcionando corretamente, o teste passa. Se alguém alterar a lógica do cálculo e quebrar algo, o teste falha imediatamente e você sabe exatamente onde está o problema.

Testando regras de negócio com pytest.raises

Nem sempre o que você quer testar é se um valor está correto. Às vezes você quer garantir que o seu sistema vai dar o erro certo quando receber uma entrada inválida. Isso é testar regras de negócio.

No nosso carrinho, por exemplo, não faz sentido aceitar um desconto de 120% ou de -20%. Então, além de adicionar uma validação no código com raise ValueError, você também precisa de um teste que confirme que essa validação está funcionando. É aí que entra o pytest.raises:

Python
import pytest

def test_desconto_fora_intervalo():
    carrinho = CarrinhoDeCompras()
    carrinho.adicionar_produto(Produto('Notebook', 3000))
    with pytest.raises(ValueError):
        carrinho.calcular_total(desconto_percentual=120)

Nesse teste, o PyTest verifica se a chamada dentro do with levanta um ValueError. Se levantar, o teste passa. Se não levantar, ou se levantar um tipo de erro diferente, o teste falha. Simples assim.

Ícone PythonPython Impressionador

Você vai aprender a linguagem de Programação que mais cresce no Mundo para fazer automações incríveis, desenvolver sites, fazer análises de dados, trabalhar com Ciência de Dados, Inteligência Artificial, mesmo que você nunca tenha tido nenhum contato com Programação na vida.

Começar agoraSeta para a direita
Fundo PythonTelas Python
Luz Python

Fixtures: reutilize configurações sem repetir código

Você vai perceber rapidamente que em praticamente todo teste você precisa criar um carrinho, adicionar produtos e deixar ele pronto para ser testado. Se você copiar esse bloco de setup para cada função de teste, vai acabar com um arquivo cheio de código repetido, difícil de manter e fácil de errar.

O princípio DRY (Don't Repeat Yourself) se aplica diretamente aqui. A solução do PyTest para isso se chama fixture. Para se aprofundar em como organizar bem seu código Python antes de testar, o artigo 4 Boas Práticas em Python traz ótimas referências.

Como criar e usar uma fixture no PyTest

Uma fixture é uma função com o decorator @pytest.fixture que executa o setup necessário e retorna o objeto pronto para os testes usarem. O PyTest recria essa função a cada teste que a recebe como parâmetro, o que garante que cada teste começa sempre do zero, sem herdar estado de outros testes.

Python
import pytest
from carrinho import Produto, CarrinhoDeCompras

@pytest.fixture
def carrinho_cheio():
    carrinho = CarrinhoDeCompras()
    carrinho.adicionar_produto(Produto('Notebook', 3000))
    carrinho.adicionar_produto(Produto('Teclado', 500))
    return carrinho

def test_calcular_total_sem_desconto(carrinho_cheio):
    assert carrinho_cheio.calcular_total() == 3500

Repare que a função de teste recebe carrinho_cheio como parâmetro. O PyTest identifica automaticamente que esse parâmetro é o nome de uma fixture e injeta o objeto retornado por ela. Você não chama a fixture manualmente, o PyTest faz isso por você.

Parametrize: teste múltiplos cenários com uma função

Imagine que você quer testar o cálculo de desconto do carrinho com três valores diferentes: 10%, 20% e 50%. Sem parametrize, você criaria três funções quase idênticas, mudando só os valores. Com o parametrize, você escreve a função uma única vez e passa todos os cenários que quer testar.

Quando e como usar o @pytest.mark.parametrize

O decorator @pytest.mark.parametrize recebe duas informações: os nomes das variáveis que vão mudar e uma lista de tuplas com os valores de cada cenário. O PyTest gera um caso de teste separado para cada tupla, com nome e resultado independentes.

Python
@pytest.mark.parametrize('valor_desconto, valor_esperado_carrinho',
    [(10, 3150), (20, 2800), (50, 1750)])
def test_calcular_total_com_desconto(carrinho_cheio, valor_desconto, valor_esperado_carrinho):
    total = carrinho_cheio.calcular_total(desconto_percentual=valor_desconto)
    assert total == valor_esperado_carrinho

Quando você rodar pytest -v, vai ver três testes listados separadamente, cada um com seu par de valores entre colchetes. Se um falhar, o PyTest te diz exatamente qual combinação de parâmetros causou o problema, sem precisar debugar um loop inteiro.

Você pode combinar fixtures e parametrize na mesma função: a fixture garante o setup e o parametrize garante os diferentes cenários. É assim que os testes ficam ao mesmo tempo organizados e completos.

Mock: simule APIs e dependências externas

Aqui chegamos na funcionalidade mais poderosa do conjunto. Imagine que você tem um sistema de checkout que, ao finalizar uma compra, se comunica com um gateway de pagamento externo, como o Mercado Pago ou a Stone. Essa comunicação envolve uma chamada de API que pode demorar segundos e cria registros reais no banco de dados.

Você não pode rodar um teste desse tipo centenas de vezes por dia. Seria lento, geraria cobranças falsas e sobrecarregaria a API do banco. E também não seria possível simular facilmente o cenário em que o pagamento é recusado. É para resolver exatamente isso que existe o Mock.

Como criar um Mock de gateway de pagamento

O Mock é um clone controlado de uma classe real. Você define qual valor uma função dele vai retornar, sem precisar executar o código real dela. Assim, você consegue testar toda a lógica do seu Checkout sem fazer nenhuma chamada real ao banco.

Python
from unittest.mock import Mock
from checkout import GatewayDePagamento, Checkout

def test_compra_com_sucesso(carrinho_cheio):
    mock_gateway = Mock(GatewayDePagamento)
    mock_gateway.cobrar.return_value = True  # simula aprovação
    checkout = Checkout(mock_gateway)
    cartao_falso = '1234-5678-9123-1234'
    resultado = checkout.finalizar_compra(carrinho_cheio, cartao_falso)
    assert resultado == 'Sucesso: Pagamento aprovado'

Repare: o Checkout recebe o mock_gateway como se fosse o gateway real. Toda a lógica do Checkout é executada de verdade, a única diferença é que quando ele chama gateway.cobrar(), a resposta vem do Mock, não da API do banco. O teste roda em milissegundos, em vez de segundos. Para mais detalhes sobre as opções do Mock, a documentação oficial do unittest.mock é a referência completa.

Verifique parâmetros e frequência de chamada

O Mock vai além de retornar valores fixos. Ele também registra como foi chamado, o que te permite verificar se a função recebeu os parâmetros corretos e se foi chamada o número de vezes esperado.

Codigo
mock_gateway.cobrar.assert_called_once_with(3500, cartao_falso)

Essa linha verifica duas coisas ao mesmo tempo: que a função cobrar foi chamada exatamente uma vez (não duas, não zero) e que foi chamada com os valores corretos, 3500 e o cartão falso. Se alguém, sem querer, alterar o valor passado para o gateway ou chamar a função de cobrança mais de uma vez, esse teste vai pegar o erro imediatamente.

É assim que você testa cenários de pagamento de forma completa, rápida e sem efeitos colaterais no ambiente real.

Conclusão

O PyTest transforma a forma como você garante a qualidade do seu código Python. Com ele, você sai de um processo manual e sujeito a falhas humanas para um sistema automatizado que roda todos os testes com um comando, aponta exatamente onde está o problema e permite evoluir o seu sistema com muito mais segurança. Fixtures deixam seu código de teste limpo, parametrize multiplica a cobertura sem repetição e Mock resolve o problema das dependências externas sem complicar nada.

Se você quer se aprofundar em Python e aprender a usar essas e outras ferramentas no nível que o mercado de trabalho exige, conheça o Python Impressionador, o curso completo da Hashtag Treinamentos para você ir do básico ao avançado e se tornar uma referência em Python na sua empresa ou em processos seletivos.

Além disso, se preferir esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!

O que você aprende neste vídeo

Resposta rápida: O PyTest acha e roda os testes sozinho pela convenção de nomes: arquivo e função começando com test. A verificação é o assert puro, sem classe nem estrutura extra. Sobre essa base vêm os três recursos que tiram a repetição: fixture prepara o cenário sem contaminar um teste com o outro, parametrize roda vários casos numa função só, e mock substitui a dependência externa.

Neste vídeo (60 min):

  • 1:00 - Por que testar: o exemplo do carrinho de compras de um e-commerce.
  • 3:00 - A lógica do desconto no carrinho, que é o que vai ser testado a aula inteira.
  • 5:00 - O jeito antigo, com print, e por que ele não escala.
  • 8:00 - Instalar a ferramenta pelo gerenciador de pacotes.
  • 8:30 - A convenção de nomes: arquivo e função começando com test_.
  • 9:30 - O assert no lugar do print, e o que ele verifica.
  • 13:00 - Rodar todos os testes com um comando só, e como ler a saída.
  • 15:00 - Quando falha, ele aponta qual teste caiu e a diferença de valor.
  • 17:00 - Travar a regra de negócio levantando um erro dentro da própria função.
  • 22:00 - Testar que o erro acontece, e não que ele deixa de acontecer.
  • 27:00 - Fixture: preparar o cenário uma vez e reaproveitar sem um teste contaminar o outro.
  • 31:00 - Parametrize: rodar vários cenários dentro de uma função de teste só.
  • 39:00 - Mock: substituir a dependência externa por uma cópia com resposta fixa.
  • 56:00 - Verificar que a função foi chamada uma vez e com os parâmetros certos.

Trechos do vídeo:

  • A fixture roda de novo a cada teste, então o cenário sempre começa limpo. É isso que permite compartilhar o preparo entre funções sem que um teste enxergue o que o anterior deixou.
  • O mock não descarta a classe testada: ele clona e fixa a resposta de uma função só. Assim dá para exercitar a lógica inteira sem chamar a API externa nem esperar o tempo dela.

FAQ - Perguntas Frequentes sobre PyTest

1. Preciso saber orientação a objetos para usar o PyTest?

Não obrigatoriamente, mas ajuda bastante. O PyTest funciona com qualquer função Python, incluindo funções simples sem classes. Para projetos com classes, como o exemplo do carrinho deste artigo, entender o básico de orientação a objetos em Python vai tornar o aprendizado muito mais fluido.

2. O PyTest substitui o unittest nativo do Python?

Os dois podem coexistir. O PyTest é compatível com testes escritos no estilo do unittest e oferece uma sintaxe mais simples e uma saída de erros mais legível. Na prática, a maioria dos times que migra para o PyTest não sente falta do unittest, mas não é preciso reescrever nada.

3. Quantos testes devo criar por função?

A recomendação é ter pelo menos um teste para cada comportamento esperado da função: o caso normal, os casos extremos e os casos de erro. Para funções com muitos cenários possíveis, o parametrize resolve bem sem multiplicar o número de funções de teste.

Hashtag Treinamentos

Para acessar outras publicações de Python, clique aqui!


Quer aprender mais sobre Python com um minicurso básico gratuito?

Posts mais recentes de Python

Posts mais recentes da Hashtag Treinamentos