Pular para o conteúdo principal

Tutorial do pytest-mock: Guia de simulação para iniciantes em Python

Saiba como usar o pytest-mock, desde a configuração e os fundamentos até as técnicas avançadas para testes práticos.
Actualizado 19 de dez. de 2024  · 15 min de leitura

À medida que seus projetos Python aumentam em complexidade e tamanho, também aumenta a importância de testes robustos. Os testes ajudam a garantir que o código funcione conforme o esperado e mantenham a qualidade em todas as partes do aplicativo.

Quando seu aplicativo interage com diferentes tipos de bancos de dados ou APIs externas, como OpenAI GPT ou Anthropic Claude, testar essas dependências pode se tornar um desafio. É nesse ponto que a zombaria é útil.

A simulação permite que você simule objetos, funções e comportamentos, tornando seus testes mais isolados e previsíveis. Ele também permite que você se concentre na função específica que deseja testar, isolando-a de dependências que poderiam introduzir complexidade ou comportamento inesperado.

Neste blog, você aprenderá a usar o pytest-mock, um poderoso plugin para pytest, para implementar mocks em seus testes Python de forma eficiente. Aofinal deste tutorial, você estará pronto para adicionar técnicas de simulação aos seus casos de teste, tornando-os mais eficientes.

O que é pytest-mock?

O pytest-mock é um plug-in para a popular estrutura de teste Python pytest que fornece acesso fácil aos recursos de simulação.y. Ele se baseia no unittest incorporado do Python, simplificando o processo de simulação durante o teste.

logotipo da estrutura pytest

logotipo da estrutura pytest

pytest-mock melhora a legibilidade e facilita a implementação de simulações em testes com uma abordagem mais nativa de testes. Se você precisa simular funções individuais, métodos de classe ou objetos inteiros, o pytest-mock oferece a flexibilidade necessária para testes eficazes sem a complexidade extra.

Um dos principais motivos da legibilidade do pytest-mock é seu estilo de teste declarativo, que permite que você especifique as chamadas e os comportamentos esperados diretamente nos testes. Isso torna o código mais fácil de escrever, ler e manter, ao mesmo tempo em que permanece resiliente às mudanças de implementação que não alteram o comportamento externo do código.

Torne-se um desenvolvedor Python

Adquira as habilidades de programação de que todos os desenvolvedores de Python precisam.
Comece a Aprender De Graça

Configuração do pytest-mock

Criar um ambiente virtual para o seu projeto é uma prática recomendada para garantir que as dependências sejam isoladas. Se ainda não tiver configurado um novo ambiente, você poderá criar um ambiente conda usando os seguintes comandos:

# create a conda environment
conda create --name yourenvname python=3.11
# activate conda environment
conda activate yourenvname

Instalação do pytest e do pytest-mock

Instale pytest e pytest-mock diretamente em seu ambiente usando pip ou conda:

pip install pytest
pip install pytest-mock

Confirmação da instalação

Depois de instalar pytest e pytest-mock, verifique se a instalação foi bem-sucedida executando o seguinte comando:

pytest --version

Noções básicas de simulação com pytest-mock

A simulação pode ser considerada como a criação de uma versão "fictícia" de um componente que imita o comportamento real. 

Isso é especialmente útil quando você precisa testar como as funções ou classes interagem com componentes externos, como bancos de dados ou APIs externas.

Introdução à simulação

Um objeto de simulação simula o comportamento de um objeto real. É frequentemente usado em testes de unidade para isolar componentes e testar seu comportamento sem executar o código dependente. 

Por exemplo, você pode usar um objeto mock para simular uma chamada de banco de dados sem realmente se conectar ao banco de dados.

A simulação é particularmente útil quando o objeto real:

  • É difícil de criar ou configurar.
  • Consome muito tempo de uso (por exemplo, acessar um banco de dados remoto).
  • Tem efeitos colaterais que devem ser evitados durante o teste (por exemplo, enviar e-mails, incorrer em custos).

Usando o dispositivo de simulação

pytest-mock fornece um acessório mocker, facilitando a criação e o controle de objetos de simulação. O acessório mocker é útil e se integra aos seus testes pytest.

O que são luminárias? 

Em Python, os acessórios são componentes reutilizáveis que configuram e removem os recursos necessários para os testes, como bancos de dados ou arquivos. Eles garantem a consistência, reduzem a duplicação e simplificam a configuração do teste.

Vamos ver um exemplo:

def fetch_weather_data(api_client):
    response = api_client.get("https://api.weather.com/data")
    if response.status_code == 200:
        return response.json()
    else:
        return None

A função fetch_weather_data depende de uma API meteorológica externa para recuperar dados, mas chamar a API durante os testes pode gerar custos desnecessários. 

Para evitar isso, você pode usar a simulação para simular o comportamento da API em seus testes, garantindo que o aplicativo seja testado sem fazer chamadas externas. Veja como a simulação pode ajudar:

def test_fetch_weather_data(mocker):
    # Create a mock API client
    mock_api_client = mocker.Mock()

    # Mock the response of the API client
    mock_response = mocker.Mock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"temperature": "20C", "condition": "Sunny"}

    # Set the mock API client to return the mocked response
    mock_api_client.get.return_value = mock_response

    # Call the function with the mock API client
    result = fetch_weather_data(mock_api_client)

    # Assert that the correct data is returned
    assert result == {"temperature": "20C", "condition": "Sunny"}

    mock_api_client.get.assert_called_once_with("https://api.weather.com/data")

Em test_fetch_weather_data(), usamos o acessório mocker para criar um cliente de API simulado mock_api_client e um mock_response. A resposta simulada é configurada para imitar uma chamada de API real, retornando um código de status 200 e dados JSON contendo informações meteorológicas. 

O método get do cliente de API simulado é definido para retornar essa resposta simulada. Quando o fetch_weather_data() é testado, ele interage com o cliente de API simulado em vez de fazer uma chamada de API real. 

Essa abordagem garante que a função se comporte conforme o esperado e, ao mesmo tempo, mantém os testes rápidos, econômicos e independentes de sistemas externos. 

A instrução assert no final verifica se os dados retornados correspondem à saída esperada.

A última linha, mock_api_client.get.assert_called_once_with("https://api.weather.com/data"), verifica especificamente se o método get do cliente de API simulado foi chamado exatamente uma vez e com o argumento correto. Isso ajuda a confirmar que a função está interagindo com a API da maneira esperada, acrescentando uma camada extra de validação ao teste.

Casos de uso comuns do pytest-mock

A simulação é útil em vários cenários, incluindo testes de unidade em que você precisa isolar funções ou componentes específicos. Vamos dar uma olhada em alguns casos de uso comuns:

1. Funções de simulação ou métodos de classe

Talvez você precise testar uma função que dependa de outras funções ou métodos, mas deseja controlar o comportamento deles durante o teste. A simulação permite que você substitua essas dependências por um comportamento predefinido:

# production code
def calculate_discount(price, discount_provider):
    discount = discount_provider.get_discount()
    return price - (price * discount / 100)

# test code
def test_calculate_discount(mocker):
    # Mock the get_discount method
    mock_discount_provider = mocker.Mock()
    mock_discount_provider.get_discount.return_value = 10  # Mocked discount value
    # Call the function with the mocked dependency
    result = calculate_discount(100, mock_discount_provider)
    # Assert the calculated discount is correct
    assert result == 90
    mock_discount_provider.get_discount.assert_called_once()

Nesse teste, o método get_discount() do discount_provider é simulado para retornar um valor predefinido (10%). Isso isola a função calculate_discount() da implementação real de discount_provider.

2. Código de simulação dependente do tempo

Ao testar funções que envolvem tempo ou atrasos, a simulação de métodos relacionados ao tempo ajuda a evitar a espera ou a manipulação do tempo real:

# production code
import time
def long_running_task():
    time.sleep(5)  # Simulate a long delay
    return "Task Complete"

# test code
def test_long_running_task(mocker):
    # Mock the sleep function in the time module
    mocker.patch("time.sleep", return_value=None)
    # Call the function
    result = long_running_task()
    # Assert the result is correct
    assert result == "Task Complete"

Aqui, o site time.sleep é corrigido para contornar o atraso real durante o teste. Isso garante que o teste seja executado rapidamente, sem esperar 5 segundos.

3. Atributos de objetos de simulação

Às vezes, você pode querer simular o comportamento dos atributos ou propriedades de um objeto para simular diferentes estados durante o teste:

# production code
class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    @property
    def is_adult(self):
        return self.age >= 18

# test code
def test_user_is_adult(mocker):
    # Create a User object
    user = User(name="Alice", age=17)
    # Mock the is_adult property
    mocker.patch.object(User, "is_adult", new_callable=mocker.PropertyMock, return_value=True)
    # Assert the mocked property value
    assert user.is_adult is True

Neste exemplo, a propriedade is_adult() da classe User é simulada para retornar True, independentemente da idade real. Isso é útil para testar cenários que dependem de estados de objetos.

Técnicas avançadas de simulação com pytest-mock

Quando você já estiver familiarizado com a simulação básica, o site pytest-mock oferece recursos avançados para lidar com cenários mais complexos.

1. Efeitos colaterais simulados

Os simuladores podem simular mais do que apenas valores de retorno - eles também podem replicar comportamentos como a geração de exceções ou a alteração dinâmica dos valores de retorno. Para isso, você pode adicionar "efeitos colaterais" aos mocks:

import pytest

# production code
def process_payment(payment_gateway, amount):
    response = payment_gateway.charge(amount)
    if response == "Success":
        return "Payment processed successfully"
    else:
        raise ValueError("Payment failed")

# test code
def test_process_payment_with_side_effects(mocker):
    # Mock the charge method of the payment gateway
    mock_payment_gateway = mocker.Mock()
    # Add side effects: Success on first call, raise exception on second call
    mock_payment_gateway.charge.side_effect = ["Success", ValueError("Insufficient funds")]
    # Test successful payment
    assert process_payment(mock_payment_gateway, 100) == "Payment processed successfully"
    # Test payment failure
    with pytest.raises(ValueError, match="Insufficient funds"):
        process_payment(mock_payment_gateway, 200)
    # Verify the mock's behavior
    assert mock_payment_gateway.charge.call_count == 2

A propriedade side_effect permite que a simulação retorne valores diferentes ou levante exceções em chamadas subsequentes. Neste exemplo, a primeira chamada para charge retorna "Success", enquanto a segunda chamada gera um ValueError. Isso é útil para testar vários cenários em um único teste.

2. Espionagem de funções

Spying permite que você acompanhe como uma função real foi chamada, incluindo o número de chamadas e os argumentos passados. Isso é particularmente útil quando você deseja garantir que uma função seja chamada conforme o esperado e, ao mesmo tempo, continue executando sua lógica original:

# production code
def log_message(logger, message):
    logger.info(message)
    return f"Logged: {message}"

# test code
def test_log_message_with_spy(mocker):
    # Spy on the info method of the logger
    mock_logger = mocker.Mock()
    spy_info = mocker.spy(mock_logger, "info")
    # Call the function
    result = log_message(mock_logger, "Test message")
    # Assert the returned value
    assert result == "Logged: Test message"
    # Verify the spy behavior
    spy_info.assert_called_once_with("Test message")
    assert spy_info.call_count == 1

O método spy() mantém o controle de como um método real é chamado sem alterar seu comportamento. Aqui, espionamos o método info do registrador para garantir que ele seja chamado com a mensagem correta e, ao mesmo tempo, permitir que o método seja executado normalmente.

Práticas recomendadas para usar o pytest-mock

A simulação é poderosa, mas usá-la sem cuidado pode causar problemas em seus testes. Aqui estão algumas práticas recomendadas que eu recomendo que você siga:

1. Evite o excesso de zombaria

O uso de muitos mocks pode levar a testes frágeis que quebram quando o código interno muda, mesmo que o comportamento externo não mude. Esforce-se para simular apenas o que for necessário e confie em implementações reais sempre que possível.

O excesso de acoplamento pode fazer com que seus testes sejam fortemente acoplados aos detalhes de implementação do código, o que significa que até mesmo uma pequena refatoração pode fazer com que os testes falhem desnecessariamente. Em vez disso, concentre-se em simular apenas as partes do sistema que são externas ou têm efeitos colaterais significativos.

2. Use convenções de nomenclatura claras

Ao fazer a simulação de objetos, use nomes descritivos para suas simulações. Isso torna seus testes mais legíveis e fáceis de manter. Por exemplo, em vez de usar nomes genéricos como mock_function(), use algo mais descritivo, como mock_api_response().

As convenções de nomenclatura claras ajudam os outros a entender a finalidade de cada simulação, reduzindo a confusão e facilitando a manutenção do conjunto de testes ao longo do tempo.

3. Mantenha os testes simples e focados

Cada teste deve se concentrar em um único comportamento ou cenário. Isso simplifica a depuração e mantém seus testes fáceis de entender. Um teste bem focado tem maior probabilidade de fornecer feedback claro quando algo dá errado, facilitando a identificação e a correção de problemas.

Conclusão

Neste blog, exploramos os conceitos básicos da simulação com pytest-mock e aprendemos a usá-la para aprimorar nossos testes em Python. Cobrimos tudo, desde a simulação básica de funções e métodos até técnicas mais avançadas, como a adição de efeitos colaterais e a espionagem de funções.

A simulação é uma ferramenta essencial para a criação de testes confiáveis e de fácil manutenção, especialmente quando você trabalha com sistemas complexos ou dependências externas. Ao incorporar o pytest-mock em seus projetos, você pode escrever testes mais isolados, previsíveis e fáceis de manter.

Para continuar expandindo suas habilidades de teste, recomendo que você confirao curso gratuito Introduction to Testing in Python no Datacamp.

Desenvolver habilidades de aprendizado de máquina

Eleve suas habilidades de aprendizado de máquina ao nível de produção.

Perguntas frequentes

Como a simulação é diferente do teste de componentes reais?

A simulação substitui componentes reais por outros simulados, permitindo testes isolados e econômicos sem depender de sistemas externos ou dados reais.

Posso usar o pytest-mock com outras estruturas de teste?

pytest-mock foi projetado especificamente para o pytest e se integra perfeitamente a ele, mas se baseia no unittest.mock do Python, que pode ser usado de forma independente.

Os acessórios são obrigatórios para a simulação no pytest-mock?

Embora os acessórios como o mocker aumentem a eficiência da simulação, você ainda pode usar o unittest.mock diretamente no pytest sem acessórios, embora com menos integração.


Moez Ali's photo
Author
Moez Ali
LinkedIn
Twitter

Cientista de dados, fundador e criador do PyCaret

Temas

Aprenda mais sobre Python com estes cursos!

curso

Introduction to Python for Developers

3 hr
35.9K
Master the fundamentals of programming in Python. No prior knowledge required!
Ver DetalhesRight Arrow
Iniciar Curso
Ver maisRight Arrow
Relacionado

blog

Como aprender Python do zero em 2024: um guia especializado

Descubra como aprender Python, suas aplicações e a demanda por competências em Python. Comece sua jornada em Python hoje mesmo ​com nosso guia detalhado.
Matt Crabtree's photo

Matt Crabtree

19 min

tutorial

Como usar o Pytest para testes de unidade

Explore o que é o Pytest e para que ele é usado, comparando-o com outros métodos de teste de software.
Kurtis Pykes 's photo

Kurtis Pykes

17 min

tutorial

Tutorial do Python pandas: O guia definitivo para iniciantes

Você está pronto para começar sua jornada com os pandas? Aqui está um guia passo a passo sobre como você pode começar.
Vidhi Chugh's photo

Vidhi Chugh

15 min

tutorial

Configuração do VSCode para Python: Um guia completo

Experimente uma forma simples, divertida e produtiva de desenvolvimento em Python, aprendendo sobre o VSCode e suas extensões e recursos.
Abid Ali Awan's photo

Abid Ali Awan

16 min

tutorial

Tutorial de como executar scripts Python

Saiba como executar um script Python a partir da linha de comando e também como fornecer argumentos de linha de comando ao seu script.
Aditya Sharma's photo

Aditya Sharma

10 min

tutorial

Desenvolvimento de back-end em Python: Um guia completo para iniciantes

Este guia completo ensina a você os fundamentos do desenvolvimento de back-end em Python. Aprenda conceitos básicos, estruturas e práticas recomendadas para você começar a criar aplicativos da Web.
Oluseye Jeremiah's photo

Oluseye Jeremiah

26 min

See MoreSee More