Ответ
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, не делая реального сетевого запроса.
Ответ 18+ 🔞
А, моки, блядь! Ну это ж классика, ёпта! Сейчас объясню на пальцах, чтобы даже мартышлюшка поняла.
Вот представь: у тебя есть функция, которая полезла в интернет за данными. А интернет — он, сука, нестабильный, как твоё настроение по понедельникам. То ли ответит, то ли нет, то ли вообще тебе в рот чих-пых пришлёт. И как тестить такую функцию? Ждать, пока облака на небе правильно встанут? Не, чувак.
Вот тут и выходят на сцену моки — это такие подставные ублюдки, которые притворяются настоящими объектами. Их задача — изолировать твой код от всей этой внешней хуйни вроде баз данных, API или файловой системы. Чтобы ты мог проверить свою логику, а не стабильность интернета провайдера.
Зачем это, блядь, нужно?
- Изоляция, ёбана! Чтобы тестить один кусок, а не всю систему разом. Как будто ты ремонтируешь двигатель, а не разбираешь всю тачку до винтика.
- Предсказуемость. Ты сам решаешь, что этот подставной объект вернёт: данные, ошибку или просто послать тебя нахуй. Можно проверить все возможные сценарии.
- Скорость. Не надо ждать, пока база данных почешется или API другого сервиса ответит. Всё происходит мгновенно, как пощёчина.
- Проверка взаимодействий. Ты можешь убедиться, что твой код правильно тыкает палкой в другие объекты, с нужными аргументами и нужное количество раз.
Смотри, как это выглядит на практике с 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. Лепим муляж ответа от requests.get
mock_response = mocker.Mock()
# Говорим муляжу: "Когда у тебя вызовут .json(), верни вот это"
mock_response.json.return_value = {"id": 1, "name": "John Doe"}
# А метод raise_for_status пусть просто молча делает вид, что всё ок
mock_response.raise_for_status.return_value = None
# 2. Подменяем (patch) сам метод requests.get на нашу заглушку
# Теперь когда кто-то вызовет requests.get, сработает наша подстава
mocker.patch('requests.get', return_value=mock_response)
# 3. Вызываем нашу родную функцию. Она даже не подозревает, что её кинули!
user_data = get_user_data(1)
# 4. Проверяем: получили ли мы то, что подсунули, и был ли вызов с правильными аргументами
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. Это и есть сила моков, блядь! Ты избавляешься от непредсказуемости внешнего мира и тестируешь только свою логику. Красота, ёпта!