Что такое мок-объект и как его использовать в тестах?

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

Использование:

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

Пример: Вместо реального подключения к базе данных, мок-объект может возвращать заранее определенные данные.


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

Зачем нужны мок-объекты?

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

Как использовать мок-объекты в тестах (пример с использованием `unittest.mock` в Python):

Предположим, у нас есть функция, которая использует внешний API для получения данных:


def get_data_from_api(api_client, endpoint):
  """Получает данные из API."""
  response = api_client.get(endpoint)
  if response.status_code == 200:
    return response.json()
  else:
    raise Exception(f"API request failed with status code: {response.status_code}")
  

Чтобы протестировать эту функцию, мы можем использовать мок-объект для `api_client`:


import unittest
from unittest.mock import MagicMock

def get_data_from_api(api_client, endpoint):
  """Получает данные из API."""
  response = api_client.get(endpoint)
  if response.status_code == 200:
    return response.json()
  else:
    raise Exception(f"API request failed with status code: {response.status_code}")


class TestGetDataFromApi(unittest.TestCase):
  def test_get_data_from_api_success(self):
    # Создаем мок-объект для api_client
    mock_api_client = MagicMock()

    # Настраиваем поведение мок-объекта:
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"data": "some data"}  # что возвращает response.json()

    # Настраиваем возвращаемое значение mock_api_client.get(endpoint)
    mock_api_client.get.return_value = mock_response

    # Вызываем функцию, передавая ей мок-объект
    result = get_data_from_api(mock_api_client, "/some/endpoint")

    # Проверяем, что функция вернула ожидаемый результат
    self.assertEqual(result, {"data": "some data"})

    # Проверяем, что метод get был вызван с правильным аргументом
    mock_api_client.get.assert_called_once_with("/some/endpoint")


  def test_get_data_from_api_failure(self):
    # Создаем мок-объект для api_client
    mock_api_client = MagicMock()

    # Настраиваем поведение мок-объекта:
    mock_response = MagicMock()
    mock_response.status_code = 500

    # Настраиваем возвращаемое значение mock_api_client.get(endpoint)
    mock_api_client.get.return_value = mock_response

    # Вызываем функцию и проверяем, что она выбрасывает исключение
    with self.assertRaisesRegex(Exception, "API request failed with status code: 500"):
      get_data_from_api(mock_api_client, "/some/endpoint")

    # Проверяем, что метод get был вызван с правильным аргументом
    mock_api_client.get.assert_called_once_with("/some/endpoint")



if __name__ == '__main__':
  unittest.main()
  

Основные методы и атрибуты `unittest.mock`:

  • `MagicMock`: Гибкий мок-объект, который может имитировать любой объект, включая функции, методы и классы. Поддерживает магические методы (например, `__str__`, `__len__`).
  • `Mock`: Базовый класс для создания мок-объектов. Не поддерживает магические методы.
  • `return_value`: Определяет значение, которое должен вернуть мок-объект при вызове.
  • `side_effect`: Определяет функцию или исключение, которое должен вызвать мок-объект при вызове. Это может быть функция, которая выполняется при каждом вызове мок-объекта. Если установить значение как исключение, то исключение будет выброшено при вызове мок-объекта.
  • `assert_called_once_with(*args, **kwargs)`: Проверяет, что мок-объект был вызван ровно один раз с указанными аргументами.
  • `assert_called_with(*args, **kwargs)`: Проверяет, что мок-объект был вызван хотя бы один раз с указанными аргументами.
  • `call_count`: Возвращает количество вызовов мок-объекта.

Другие библиотеки для мокирования:

  • `pytest-mock` (плагин для pytest)
  • `mocker`
  • `doublex`

Важно: Мок-объекты следует использовать с осторожностью. Чрезмерное использование моков может привести к тому, что тесты будут проверять только детали реализации, а не поведение. Следует стараться фокусироваться на тестировании взаимодействия между модулями, а не на тестировании деталей реализации отдельных модулей.

0