Мокирование (mocking) в pytest

Как использовать мокирование в pytest

Мокирование — это метод в программировании, который позволяет заменять реальные объекты в системе на созданные имитации, которые имитируют поведение реальных объектов.

Это особенно полезно в тестировании, поскольку позволяет разработчикам проверять функциональность модуля в изоляции, не заботясь о том, как ведут себя внешние зависимости, такие как базы данных, API и другие сервисы.

Зачем это нужно

  • Изоляция тестов. Моки позволяют тестировать код в изоляции от внешнего мира, что делает тесты быстрее и предсказуемыми.
  • Имитация сложного поведения. Моки могут имитировать любое поведение, в том числе ошибки, что позволяет проверять обработку ошибок.
  • Контроль над данными теста. С моками вы можете задать конкретные возвращаемые значения для функций или методов.

Как это работает

На практике, когда вы создаете мок для определенной функции или метода, вы на самом деле создаете поддельный объект, который может быть настроен так, чтобы вести себя определенным образом. Когда ваш код затем вызывает эту функцию или метод, он на самом деле взаимодействует с моком, а не с реальной реализацией.

Пример с использованием unittest.mock (встроено в стандартную библиотеку Python):

from unittest.mock import Mock

# Создание мока
mocked_function = Mock()

# Настройка мока для возврата определенного значения
mocked_function.return_value = "Hello, Mock!"

# Вызов мокированной функции
result = mocked_function()

# Проверка результата
assert result == "Hello, Mock!"

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

pytest-mock — это плагин для pytest, который обеспечивает удобный интерфейс для мокирования с использованием unittest.mock. Этот плагин делает процесс мокирования более простым и "питоновским".

Установим плагин: pip install pytest-mock

К примеру, у вас есть модуль some_module.py с функцией, которая делает вызов к внешнему сервису:

# some_module.py

def get_data_from_service():
    # Этот код делает запрос к внешнему сервису (просто для примера)
    return "Real Data"

Создаем тест:

# test_some_module.py

from some_module import get_data_from_service

def test_get_data(mocker):  # Обратите внимание на аргумент mocker
    # Мокируем функцию get_data_from_service
    mock_get_data = mocker.patch("some_module.get_data_from_service")
    mock_get_data.return_value = "Mocked Data"

    result = get_data_from_service()

    # Проверяем, что функция была вызвана
    mock_get_data.assert_called_once()
    assert result == "Mocked Data"

В этом примере, благодаря pytest-mock, вы можете использовать аргумент mocker в своих тестах. Этот объект предоставляет все методы и функции, доступные в unittest.mock, но с упрощенным синтаксисом.

Теперь, запустив pytest, вы увидите, что ваш тест успешно выполнен, и реальный вызов функции get_data_from_service() не был произведен, а был использован мок.

На что стоит обратить внимание

  1. Не злоупотребляйте моками. Их следует использовать только тогда, когда это действительно необходимо, например, для изоляции внешних зависимостей.
  2. Удостоверьтесь, что моки актуальны. Если реальный объект изменяется, мок должен отражать эти изменения.
  3. Будьте внимательны с проверками. Помимо проверки возвращаемых значений, вы также можете проверять, сколько раз был вызван мок, с какими аргументами и так далее.

Мокирование — мощный инструмент в руках разработчика, который позволяет создавать более надежные и изолированные тесты. Однако следует использовать его с умом и понимать его ограничения.

Несколько важных аспектов

Side Effects: В unittest.mock, существует метод side_effect, который позволяет вам указать функцию или исключение, которое будет вызвано/выброшено при вызове мок-объекта. Это может быть полезно, если вы хотите имитировать реальное поведение функции в разных сценариях.

mock = Mock()
mock.side_effect = ValueError("This is an error!")

Assert Calls: Помимо проверки возвращаемого значения, вы можете проверять, как именно был вызван ваш мок:

mock_function.assert_called_once()
mock_function.assert_called_with(expected_argument)

MagicMock: MagicMock — это расширение базового Mock, которое добавляет магические методы (например, __len__, __getitem__ и так далее). Это позволяет моку вести себя как другие типы объектов, такие как списки или словари.

patch.object: Хотя часто используется mocker.patch('path.to.module.function'), иногда вы можете предпочесть использовать patch.object для мокирования конкретных методов объекта, а не всего объекта целиком.

Понимание autospec: Опция autospec может быть использована в patch для автоматической спецификации мока на основе объекта или класса, который он заменяет. Это помогает гарантировать, что мок будет вести себя так же, как настоящий объект, и может предотвратить некоторые ошибки в тестах.

Осторожно с мокированием: Хотя мокирование является мощным инструментом, чрезмерное его использование может сделать ваш код сложным и трудным для понимания. Старайтесь использовать моки только тогда, когда это действительно необходимо и когда это добавляет ясность ваших тестов.

Заключение

Мокирование — мощный инструмент в руках разработчика, который позволяет создавать более надежные и изолированные тесты. Однако следует использовать его с умом и понимать его ограничения.

Содержание: