Что такое mock-объекты в Python и как они используются в тестировании с pytest?

Ответ

Mock (мок) — это объект-заглушка, который имитирует поведение реальных объектов в контролируемых условиях. Моки являются ключевым инструментом для изоляции тестируемого кода от внешних зависимостей (например, баз данных, сетевых API, файловой системы).

Основная библиотека для мокирования в Python — это unittest.mock, которая отлично интегрируется с pytest.

Зачем нужны моки?

  • Изоляция: Тестировать один компонент системы, не затрагивая другие.
  • Предсказуемость: Задавать конкретные возвращаемые значения или исключения, чтобы проверить все ветки кода.
  • Скорость: Избегать медленных операций, таких как сетевые запросы или работа с диском.
  • Проверка взаимодействий: Убедиться, что тестируемый код правильно вызывает методы зависимых объектов.

Пример использования с pytest и pytest-mock:

Плагин pytest-mock предоставляет удобную фикстуру mocker.

# a.py - модуль с функцией, которую мы хотим протестировать
import requests

def get_user_data(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    response.raise_for_status() # Вызовет исключение для плохих статусов
    return response.json()

# test_a.py - тест для функции
import pytest
import requests
from .a import get_user_data

def test_get_user_data_success(mocker):
    # 1. Создаем mock для ответа от requests.get
    mock_response = mocker.Mock()
    mock_response.json.return_value = {"id": 1, "name": "John Doe"}
    mock_response.raise_for_status.return_value = None # Указываем, что метод не должен вызывать исключение

    # 2. Подменяем (patch) метод requests.get
    mocker.patch('requests.get', return_value=mock_response)

    # 3. Вызываем нашу функцию
    user_data = get_user_data(1)

    # 4. Проверяем результат и то, что mock был вызван правильно
    assert user_data == {"id": 1, "name": "John Doe"}
    requests.get.assert_called_once_with("https://api.example.com/users/1")

def test_get_user_data_failure(mocker):
    # Подменяем requests.get, чтобы он вызывал исключение
    mocker.patch('requests.get', side_effect=requests.exceptions.HTTPError)

    # Проверяем, что наша функция корректно обрабатывает исключение
    with pytest.raises(requests.exceptions.HTTPError):
        get_user_data(2)

В этом примере мы полностью контролируем поведение requests.get, не делая реального сетевого запроса.