Curso
Tutorial do pytest-mock: Guia de simulação para iniciantes em Python
À 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
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
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
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.
Aprenda mais sobre Python com estes cursos!
Curso
Introduction to Data Science in Python
Curso
Introduction to Functions in Python

blog
Como aprender Python do zero em 2024: um guia especializado

Tutorial
Como usar o Pytest para testes de unidade
Tutorial
Configuração do VSCode para Python: Um guia completo
Tutorial
Tutorial de como executar scripts Python
Tutorial
Desenvolvimento de back-end em Python: Um guia completo para iniciantes

Oluseye Jeremiah
15 min