Módulo 13 - Testes avançados

Fixtures: preparar o cenário do teste

10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Criar uma fixture com @pytest.fixture e recebê-la por parâmetro.
  • Fazer setup e teardown com yield dentro da fixture.
  • Reutilizar a mesma fixture em vários testes sem repetição.
  • Usar fixtures embutidas, como tmp_path, para arquivos temporários.

O que é uma fixture

Muitos testes precisam do mesmo ponto de partida. Vários testes de uma classe Carrinho, por exemplo, precisam de um carrinho já criado com alguns itens. Copiar esse preparo no começo de cada teste é repetitivo e frágil: se o preparo muda, você tem que mexer em todos. A fixture resolve isso. Você escreve a preparação uma vez, numa função decorada com @pytest.fixture, e cada teste que precisar dela apenas a declara como parâmetro. O pytest percebe o nome, executa a fixture e entrega o resultado pronto.

import pytest

class Carrinho:
    def __init__(self):
        self.itens = []
    def adicionar(self, item):
        self.itens.append(item)
    def total(self):
        return len(self.itens)

@pytest.fixture
def carrinho_com_itens():
    c = Carrinho()
    c.adicionar("livro")
    c.adicionar("caneta")
    return c

def test_total(carrinho_com_itens):
    assert carrinho_com_itens.total() == 2

def test_adicionar_mais(carrinho_com_itens):
    carrinho_com_itens.adicionar("caderno")
    assert carrinho_com_itens.total() == 3

A fixture prepara o carrinho uma vez; cada teste a recebe pronta pelo nome do parâmetro.

Há um detalhe importante e tranquilizador: o pytest executa a fixture de novo para cada teste que a pede. Por isso test_adicionar_mais recebe um carrinho fresco com dois itens, não o carrinho já modificado por test_total. Cada teste começa do mesmo cenário limpo, sem interferência de outro. Esse isolamento é uma das qualidades mais importantes de uma boa suíte: um teste nunca deve depender do que outro fez, e as fixtures garantem isso de graça.

Setup e teardown com yield

Quando o cenário envolve um recurso que precisa ser liberado, como um arquivo aberto ou uma conexão, a fixture usa yield em vez de return. O código antes do yield é o setup, que prepara o recurso. O valor entregue pelo yield é o que o teste recebe. E o código depois do yield é o teardown, que o pytest executa automaticamente quando o teste termina, mesmo que ele falhe. É o mesmo espírito do context manager que você viu antes: garantir a limpeza aconteça o que acontecer.

import pytest

@pytest.fixture
def conexao_falsa():
    # setup: prepara o recurso
    conexao = {"aberta": True, "registros": []}
    print("abrindo conexao")
    yield conexao          # entrega ao teste
    # teardown: roda apos o teste, mesmo se ele falhar
    conexao["aberta"] = False
    print("fechando conexao")

def test_grava(conexao_falsa):
    conexao_falsa["registros"].append("linha 1")
    assert conexao_falsa["aberta"] is True
    assert len(conexao_falsa["registros"]) == 1

Antes do yield: setup. Depois do yield: teardown, garantido pelo pytest.

Essa estrutura com yield é o jeito idiomático de fazer setup e teardown no pytest, mais claro do que os métodos separados de frameworks antigos. Tudo que diz respeito a um recurso, o preparo e a limpeza, fica junto na mesma função, fácil de ler e de manter. Se você precisar de limpeza mesmo quando o setup falha no meio, dá para combinar com um try/finally dentro da fixture. Mas, para a maioria dos casos, o padrão setup, yield, teardown já cobre com folga.

Fixtures prontas do pytest

O pytest já traz fixtures embutidas para necessidades comuns, e vale conhecê-las antes de escrever a sua. A mais útil no dia a dia é tmp_path, que entrega um diretório temporário exclusivo daquele teste, apagado automaticamente depois. Ela é perfeita para testar código que lê e escreve arquivos sem sujar o seu projeto nem deixar lixo. Existem outras, como capsys, que captura o que o código imprime, e monkeypatch, que você verá na aula de mocks. Reaproveitar essas fixturas evita reinventar a roda.

def salvar_nome(caminho, nome):
    caminho.write_text(nome, encoding="utf-8")

def ler_nome(caminho):
    return caminho.read_text(encoding="utf-8")

# tmp_path e uma fixture embutida: um diretorio temporario so deste teste.
def test_salvar_e_ler(tmp_path):
    arquivo = tmp_path / "nome.txt"
    salvar_nome(arquivo, "Ana")
    assert ler_nome(arquivo) == "Ana"
    # o pytest apaga tmp_path sozinho apos o teste

tmp_path dá um diretório temporário isolado; nenhum arquivo real do projeto é tocado.

Teste rápido

Numa fixture do pytest que usa yield, o que é o código escrito depois do yield?

Perguntas frequentes

A fixture roda uma vez ou a cada teste?
Por padrão, o pytest executa a fixture de novo para cada teste que a solicita, garantindo um cenário fresco e isolado. Se você quiser compartilhar um recurso caro entre vários testes, pode ajustar o escopo da fixture, por exemplo para rodar uma vez por módulo, com o parâmetro scope.
Como uma fixture é entregue ao teste?
Pelo nome. Você declara a fixture como um parâmetro da função de teste, com o mesmo nome da fixture. O pytest reconhece esse nome, executa a fixture correspondente e injeta o valor no parâmetro. Não é preciso importar nem chamar nada explicitamente.
Qual a diferença entre return e yield numa fixture?
Com return, a fixture só entrega o valor, sem teardown. Com yield, ela entrega o valor no yield e executa o código após o yield como teardown, quando o teste acaba. Use yield quando houver limpeza a fazer, como fechar recursos ou desfazer um estado.
O que é o arquivo conftest.py?
É um arquivo especial onde você coloca fixtures e configurações que devem valer para vários arquivos de teste de uma pasta. O pytest o descobre automaticamente, sem import. É o lugar certo para fixtures compartilhadas, evitando repeti-las em cada arquivo de teste.
Por que usar tmp_path em vez de criar um arquivo qualquer?
Porque tmp_path dá um diretório temporário isolado por teste, que o pytest apaga sozinho depois. Isso mantém os testes independentes e não deixa lixo no seu projeto. Criar arquivos em caminhos reais arrisca conflitos entre testes e resíduos que atrapalham execuções futuras.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.