Ответ
Mock-объекты (моки) — это "поддельные" объекты, которые имитируют поведение реальных зависимостей в контролируемых условиях. Они являются ключевым инструментом для изоляции тестируемого компонента.
Основная цель их использования — заменить внешние зависимости (например, базу данных, внешнее API, файловую систему), чтобы:
- Изолировать тест: Убедиться, что тест проверяет только логику конкретного модуля, а не его зависимостей.
- Ускорить выполнение тестов: Избежать медленных операций, таких как сетевые запросы или чтение с диска.
- Сделать тесты детерминированными: Контролировать возвращаемые значения и ошибки, делая результат теста предсказуемым.
- Тестировать сложные сценарии: Эмулировать специфические состояния или ошибки, которые трудно воспроизвести с реальными объектами (например, отказ сети).
Пример с unittest.mock в Python:
Предположим, у нас есть функция, которая получает данные о погоде с внешнего API.
import requests
def get_weather_forecast():
response = requests.get("http://api.weather.com/forecast")
response.raise_for_status() # Вызовет исключение для плохих статусов
return response.json()["temperature"]
Тест с использованием patch для подмены requests.get:
from unittest.mock import patch
def test_get_weather_forecast_success():
# Создаем mock-объект для ответа API
mock_response = {"temperature": 25}
# patch заменяет requests.get на mock-объект внутри блока with
with patch('requests.get') as mock_get:
# Настраиваем, что должен вернуть наш mock
mock_get.return_value.json.return_value = mock_response
mock_get.return_value.raise_for_status.return_value = None
# Вызываем нашу функцию
temperature = get_weather_forecast()
# Проверяем результат
assert temperature == 25
# Проверяем, что requests.get был вызван с правильным URL
mock_get.assert_called_once_with("http://api.weather.com/forecast")
Ключевые инструменты в Python:
unittest.mock: Стандартная библиотека для мокинга.pytest-mock: Популярный плагин дляpytest, предоставляющий удобную фикстуруmocker.
Ответ 18+ 🔞
Слушай, а вот эти ваши моки, блядь... Это ж просто пиздец какой-то цирк, а не инструмент! Ну представь: ты пишешь код, который лезет куда-то в сеть, в базу, ещё в какую-то хуйню. А потом надо это протестировать. И что, каждый раз реально дергать апишку, которая, сука, может и не ответить, или в базе данных пиздец творится? Да ну нахуй, я не мазохист.
Вот моки — это такие подставные ублюдки, которые притворяются настоящими объектами. Ты их настраиваешь, как кукол, и они делают ровно то, что тебе нужно для теста. Никаких сюрпризов, блядь!
Зачем это, спросишь? Да похуй, спросишь ты или нет, я расскажу.
- Изоляция, ёпта! Чтобы проверить, не сломался ли твой конкретный кусок логики, а не то, что какой-то сервис у соседей лег. Отделяешь мух от котлет, блядь.
- Скорость, ядрёна вошь! Не ждать же, пока из облака ответ придет? Мок ответит мгновенно. В рот меня чих-пых, это же очевидно!
- Предсказуемость, бля! Хочешь проверить, как твой код отреагирует на ошибку 500? Скажи моку: «Верни ошибку!» — и он вернет. Хочешь пустой ответ? Пожалуйста. Никаких «ой, а сервер сегодня капризный».
- Сложные сценарии. Как, блядь, заставить реальную базу данных упасть в именно тот момент, когда ты делаешь третий запрос? Да хрен там! А моку скажешь — и он упадет. Красота!
Вот смотри, пример, чтобы совсем понятно стало. Есть у нас функция, которая лезет за погодой. Прям вот так, в лоб:
import requests
def get_weather_forecast():
response = requests.get("http://api.weather.com/forecast")
response.raise_for_status() # Вызовет исключение для плохих статусов
return response.json()["temperature"]
Ну и как это тестировать-то, а? Каждый раз погоду качать? Да ты посмотри, она же у них в Питере вечно дождь! Настроения никакого не будет для тестов.
А вот как это делают нормальные люди, с моками:
from unittest.mock import patch
def test_get_weather_forecast_success():
# Это наш подставной ответ. Сказали, что +25 — значит +25. Хоть в аду.
mock_response = {"temperature": 25}
# А вот тут магия! Говорим: «Эй, патч, иди нахуй и подмени мне `requests.get` на нашу куклу!»
with patch('requests.get') as mock_get:
# Настраиваем куклу. Говорим: «Когда у тебя вызовут .json(), верни наш словарик.
# А когда вызовут .raise_for_status(), просто молчи, как рыба об лёд».
mock_get.return_value.json.return_value = mock_response
mock_get.return_value.raise_for_status.return_value = None
# Вызываем нашу функцию. Она даже не почувствует подмены, бедолага.
temperature = get_weather_forecast()
# Проверяем, что всё ок.
assert temperature == 25
# А тут мы, хитрожопые, проверяем, что функция таки дернула правильный URL.
# Не пошла ли она, сука, погоду у хохлов спрашивать?
mock_get.assert_called_once_with("http://api.weather.com/forecast")
Вот и вся магия, блядь. В Питоне для этого есть unittest.mock из коробки. А если ты юзаешь pytest, то там вообще красота — плагин pytest-mock с фикстурой mocker, которая всё ещё проще делает. В общем, инструмент — огонь. Главное — не увлекаться и не замокать всё на свете, а то получится тест, который проверяет, как мок работает с другим моком. А это уже, простите, пиздопроебибна какая-то.