Что такое мокирование в тестировании и для чего оно используется

Ответ

Мокирование (mocking) — это техника в юнит-тестировании, при которой реальные зависимости объекта (например, модули для работы с базой данных, внешние API, файловая система) заменяются на управляемые объекты-заглушки (моки).

Основная цель — изолировать тестируемый код (SUT - System Under Test), чтобы проверить его логику в отрыве от внешних систем.

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

  1. Предсказуемость: Мок всегда возвращает заранее определенные данные, что делает тест стабильным.
  2. Скорость: Тесты выполняются быстрее, так как нет реальных сетевых запросов или обращений к диску.
  3. Изоляция: Ошибки в зависимостях не влияют на результаты теста, проверяется только логика самого юнита.
  4. Тестирование крайних случаев: Легко имитировать ошибки (например, недоступность сети или пустой ответ от БД) и проверить, как код их обрабатывает.

Пример с unittest.mock в Python:

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

import requests

def get_weather_description(city: str) -> str:
    try:
        response = requests.get(f"https://api.weather.com/{city}")
        response.raise_for_status() # Вызовет исключение для плохих статусов
        data = response.json()
        return data['weather']['description']
    except requests.RequestException:
        return "Weather data unavailable"

Чтобы протестировать эту функцию без реального HTTP-запроса, мы «мокируем» requests.get:

from unittest import TestCase
from unittest.mock import patch

# Тест
class WeatherTest(TestCase):
    @patch('__main__.requests.get') # Указываем путь к объекту для мокирования
    def test_get_weather_description_success(self, mock_get):
        # 1. Настраиваем поведение мока
        mock_response = mock_get.return_value
        mock_response.json.return_value = {"weather": {"description": "Sunny"}}
        mock_response.raise_for_status.return_value = None

        # 2. Вызываем тестируемую функцию
        description = get_weather_description("London")

        # 3. Проверяем результат и вызовы
        self.assertEqual(description, "Sunny")
        mock_get.assert_called_once_with("https://api.weather.com/London")

Ответ 18+ 🔞

А, вот эта тема про мокирование! Ну, это когда ты, блядь, в тестах не хочешь, чтобы всякая хуйня мешала — типа базы данных, которая может лежать, или апишки, которая тебе в ответ плюнет. Вместо них подсовываешь подставных уродов, которые делают только то, что ты им скажешь. Красота, ёпта!

Зачем это, спрашивается, надо, блядь?

  1. Чтобы не охуеть от неожиданностей. Мок — он как дрессированная мартышка: скажешь «принеси банан» — принесёт банан, а не нассет тебе в тапок, как это делает реальная система в пятницу вечером.
  2. Чтобы не ждать, как дурак. Запрос к настоящему API может идти секунды, а то и минуты. А мок ответит мгновенно, потому что он у тебя в оперативке сидит, хитрая жопа.
  3. Чтобы изолировать свою шайку-лейку. Если твой код сломался, ты должен быть уверен, что это он сам — мудак, а не потому что какой-то внешний сервис накрылся медным тазом.
  4. Чтобы протестировать пиздец. Хочешь проверить, как твоя функция поведёт себя, если сеть отвалится? Легко! Скажешь моку — «симулируй ошибку, сука», и он тебе её устроит. Без всяких реальных саботажей.

Смотри, как это в Питоне делается, на живом примере.

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

import requests

def get_weather_description(city: str) -> str:
    try:
        response = requests.get(f"https://api.weather.com/{city}")
        response.raise_for_status() # Вызовет исключение для плохих статусов
        data = response.json()
        return data['weather']['description']
    except requests.RequestException:
        return "Weather data unavailable"

И вот ты такой: «Хочу её потестить, но на реальный запрос идти — терпения ноль ебать, да и интернета может не быть». Берёшь и подменяешь requests.get своей куклой!

from unittest import TestCase
from unittest.mock import patch

# Тест
class WeatherTest(TestCase):
    @patch('__main__.requests.get') # Вот тут мы и говорим: "Слушай, Python, вместо настоящего requests.get подставь мою поделку"
    def test_get_weather_description_success(self, mock_get):
        # 1. Настраиваем поведение мока
        mock_response = mock_get.return_value
        mock_response.json.return_value = {"weather": {"description": "Sunny"}}
        mock_response.raise_for_status.return_value = None

        # 2. Вызываем тестируемую функцию
        description = get_weather_description("London")

        # 3. Проверяем результат и вызовы
        self.assertEqual(description, "Sunny")
        mock_get.assert_called_once_with("https://api.weather.com/London")

Видишь, какая магия? Функция думает, что сходит в интернет, а на самом деле пообщалась с нашим подставным актёром. И мы точно знаем, что она его вызвала с правильным URL и правильно разобрала его фейковый ответ. Всё чисто, изолированно и быстро, как удар током.