Мокирование возвращаемых значений функции в Python: unit-тесты

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Если требуется принудить объект мока возвращать значения по порядку, следует использовать атрибут side_effect библиотеки unittest.mock. Вам потребуется установить этот атрибут следующим образом:

Python
Скопировать код
from unittest.mock import MagicMock

mock_func = MagicMock()

# Последовательный возврат значениями: 'первый', 'второй', 'третий'
mock_func.side_effect = ['первый', 'второй', 'третий']

# Ваш последовательный вызов функции-мока
print(mock_func())  # Выведет: 'первый'
print(mock_func())  # Выведет: 'второй'
print(mock_func())  # Выведет: 'третий'

Для этого присваивается side_effect итерируемый объект с требуемыми значениями. Каждый вызов mock_func вернёт следующее по порядку значение из перечисленных.

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

Кинга Идем в IT: пошаговый план для смены профессии

Использование итераторов и итерируемых объектов в качестве side_effect

Создание mock-объекта с итерируемым объектом в side_effect

Вы можете напрямую присвоить список или любой иной итерируемый объект в side_effect:

Python
Скопировать код
mock_obj = Mock()
mock_obj.some_method.side_effect = ["Я Бэтмен!", "Нет, я Бэтмен!", "Нет, БЭТМЕН – это я..."]

Теперь mock_obj.some_method() при каждом вызове будет возвращать новую фразу о Бэтмене.

Более точное управление возвращаемыми значениями с помощью итератора

Для более точного контроля возвращаемыми значениями можно использовать сочетание функций iter() и patch(), присваивая side_effect:

Python
Скопировать код
return_values = iter(['да', 'нет', 'может быть'])
with patch('module.func', side_effect=return_values) as mocked_func:
    # Здесь вы можете выполнять свои действия
    ...

Состояние итератора сохраняется между вызовами и результаты возвращаются последовательно.

Тестирование функций с переменными возвращаемыми значениями

Управление вызовами функций без учета входных данных

Если у функции нет входных данных, можно установить разные возвращаемые значения:

Python
Скопировать код
mock_obj = Mock(return_value=None)
mock_obj.method.side_effect = ['клубничный', 'черничный', 'яблочный']

Проверка соответствия вызовов и результатов

После того как вы настроили side_effect, можно создать тесты для проверки:

  1. Количество вызовов функции соответствует ожидаемому.
  2. Последовательность возвращаемых значений соответствует массиву side_effect.

Использование исключений в side_effect

Если требуется последовательно генерировать исключения в сочетании с подходящими возвращаемыми значениями:

Python
Скопировать код
mock_obj = Mock()
mock_obj.broken_method.side_effect = ['утка', 'утка', GooseError('ГУСЬ!')]

Запомните, что при срабатывании исключения оставшиеся вызовы не выполняются, если исключение не обработано в коде теста.

Настройка параметров поведения функций

Разные входные данные соответствуют разным выходным

Если разные входные данные должны вызывать конкретные ответы:

Python
Скопировать код
mock_obj = Mock()

def side_effect(arg):
    returns = {'яблоко': 1, 'банан': 2, 'киви': 3}
    return returns.get(arg, 'Сюрприз!')

mock_obj.method.side_effect = side_effect

Это позволит возвращать разные результаты для разных входных данных.

Динамически изменяющиеся возвращаемые значения

Если необходимо имитировать динамично меняющееся поведение:

Python
Скопировать код
return_values = ['подъем', 'круиз', 'спуск', 'посадка']

def dynamic_side_effect(*args):
    return return_values.pop(0)

mock_obj.method.side_effect = dynamic_side_effect

Таким образом, можно имитировать процесс, который меняется со временем.

Визуализация

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

Python
Скопировать код
from unittest.mock import Mock

# DJ с ротационным плейлистом
dj = Mock()
dj.play.side_effect = ['🎶 Трек 1', '🎶 Трек 2', '🎶 Трек 3']

При каждом запросе пользователя будет включаться следующий трек:

Markdown
Скопировать код
📻 ➡️ 🎶 Трек 1

Следующий запрос:

Markdown
Скопировать код
📻 ➡️ 🎶 Трек 2

И следующий:

Markdown
Скопировать код
📻 ➡️ 🎶 Трек 3

Так же как и mock, который возвращает заданные заранее значения.

Управление множеством mock-объектов с помощью side_effect

Подмена методов экземпляра

При подмене методов экземпляра убедитесь, что цель подмены выбрана корректно:

Python
Скопировать код
with patch.object(SomeClass, 'method', side_effect=[...]) as mock_method:
    instance = SomeClass()  
    instance.method()  # Выполняет первый вызов
    ...

Подмена классовых и статических методов

Классовые и статические методы подменяются с помощью mock:

Python
Скопировать код
with patch.object(SomeClass, 'class_method', side_effect=[...]) as mock_class_method:
    ...
with patch.object(SomeClass, 'static_method', side_effect=[...]) as mock_static_method:
    ...

Подмена свойств с использованием side_effect

Для отображения изменений свойств используйте side_effect в сочетании с PropertyMock:

Python
Скопировать код
with patch('module.Class.property', new_callable=PropertyMock) as mock_property:
    mock_property.side_effect = [val1, val2, val3]
    ...

Полезные материалы

  1. unittest.mock — mock-объекты в Python — Документация Python — Официальное руководство по использованию библиотеки unittest.mock.
  2. Python mock multiple return values – Stack Overflow — Обсуждения на Stack Overflow о множественных возвращаемых значениях при тестировании Python.
  3. Использование библиотеки Python Mock Object – Real Python — Статья о работе с объектами мок в Python.
  4. Мокирование в Python: как улучшить юнит-тесты | Toptal — Материал, посвященный использованию мокирования в Python.
  5. Разгадка функции Patch – PyCon 2018 – YouTube — Презентация на PyCon, разъясняющая основы работы с функцией patch.
  6. Python Test | 123: GitHub Actions – Tania Allard — Подкаст с полезными советами о использовании Pytest совместно с GitHub Actions.