Mocking и Stubbing
Mock — проверяет взаимодействие («вызвали ли метод с правильными параметрами?»). Stub — возвращает предопределённый ответ («всегда отвечай успехом»). Используются, чтобы тестировать компонент изолированно, без реальных БД, API, очередей. Fake — упрощённая рабочая реализация (например, in-memory БД вместо PostgreSQL). WireMock — заглушка для HTTP API (симуляция ответов, ошибок, задержек). Риск избыточного мокинга: тесты зелёные, а реальная интеграция сломана. Риск недостаточного: тесты медленные и нестабильные.
Введение: Дублёры в тестировании
Представьте, что вы тестируете систему управления заказами, которая отправляет уведомления в SMS-шлюз. Каждый раз, когда вы запускаете тест, он отправляет реальное SMS. Это дорого, медленно и может разозлить получателей.
Вместо этого вы подменяете SMS-шлюз заглушкой (stub), которая всегда отвечает “успешно”. Или двойником (mock), который запоминает, что его вызывали, и потом вы проверяете: “А точно ли SMS был отправлен?”.
Mocking и Stubbing — это техники создания дублёров для зависимостей при тестировании. Они позволяют тестировать компонент в изоляции, не поднимая реальные базы данных, внешние API, очереди сообщений.
Для системного аналитика эти понятия важны, потому что они определяют, насколько тесты надёжны, быстры и независимы. Вы не пишете эти тесты, но вы оцениваете стратегию тестирования, интерпретируете результаты и понимаете ограничения.
Основные понятия
| Понятие | Что делает | Вопрос, на который отвечает |
|---|---|---|
| Stub (заглушка) | Возвращает предопределённый ответ | “Что вернёт зависимость?” |
| Mock (двойник) | Запоминает вызовы и проверяет их | “Вызвали ли зависимость правильно?” |
| Fake (фейк) | Упрощённая рабочая реализация | “Как зависимость работает?” |
| Spy (шпион) | Обёртка вокруг реального объекта | “Что реально происходило?” |
Stub (Заглушка)
Что это
Stub — это объект, который возвращает заранее заданные ответы на вызовы. Он не проверяет, как его вызвали. Просто возвращает то, что вы настроили.
Когда использовать
| Ситуация | Пример |
|---|---|
| Тест не проверяет взаимодействие | Проверяем только расчёт цены, не проверяем вызов SMS |
| Зависимость медленная | База данных, внешний API |
| Зависимость нестабильная | Погодный API, курс валют |
| Зависимость дорогая | Платёжный шлюз (каждый тест — реальные деньги) |
Пример из реальности
Без stub (плохо):
Тест создаёт заказ → API вызывает реальный SMS-шлюз → уходит реальное SMS → медленно, дорого, раздражает.
Со stub (хорошо):
Тест создаёт заказ → API вызывает заглушку SMS-шлюза → заглушка мгновенно отвечает “успешно” → быстро, дёшево, никому не звонит.
Что должен знать аналитик
| Вопрос | Почему важен |
|---|---|
| “Какие зависимости заглушены в тестах?” | Понимать, что реально тестируется, а что нет |
| “Есть ли тесты с реальными зависимостями?” | Интеграционные тесты нужны для проверки настоящих вызовов |
Mock (Двойник)
Что это
Mock — это объект, который запоминает, как его вызывали. После теста вы проверяете: “Был ли вызван метод?”, “С какими параметрами?”, “Сколько раз?”.
Когда использовать
| Ситуация | Пример |
|---|---|
| Тест проверяет взаимодействие | “При оплате заказа API вызывает платёжный шлюз” |
| Важно, что зависимость вызвана | “При отмене заказа API отправляет событие в Kafka” |
| Важны параметры вызова | “При создании пользователя API вызывает сервис уведомлений с правильным email” |
Пример из реальности
Mock:
Тест создаёт пользователя → API вызывает мок-сервис уведомлений → мок запоминает, что его вызвали с email “ivan@example.com”.
После теста проверка: “Был ли вызван метод отправки уведомления?” → Да. “С правильным email?” → Да.
Что должен знать аналитик
| Вопрос | Почему важен |
|---|---|
| “Что именно проверяют моки?” | Понимать, какие взаимодействия тестируются |
| “Есть ли тесты, которые проверяют, что вызов НЕ произошёл?” | Например, “при ошибке валидации не вызывать платёжный шлюз” |
Stub vs Mock
| Характеристика | Stub | Mock |
|---|---|---|
| Назначение | Возвращать данные | Проверять взаимодействие |
| Что проверяет | Состояние (результат) | Поведение (вызовы) |
| Пример | Всегда возвращает “успех” | Проверяет, что вызвали метод “оплатить” |
| Вопрос | “Что вернулось?” | “Что вызвалось?” |
Аналогия:
- Stub — кассир, который всегда говорит “билет действителен”. Неважно, кто и когда спросил.
- Mock — кассир, который записывает, кто и когда спрашивал. Потом вы проверяете журнал.
Fake (Фейк)
Что это
Fake — это упрощённая, но рабочая реализация зависимости. Например, вместо реальной базы данных — in-memory коллекция.
Когда использовать
| Ситуация | Пример |
|---|---|
| Нужна логика, не только ответы | Тест проверяет, что пользователь сохраняется в БД |
| Нужно несколько шагов | Сначала сохранить, потом прочитать, потом обновить |
Пример
Вместо PostgreSQL → in-memory словарь (HashMap).
"Сохранить пользователя" → положить в map
"Найти пользователя по ID" → достать из mapЧто даёт: Тест быстрый, не требует реальной БД, но проверяет логику сохранения и чтения.
Spy (Шпион)
Что это
Spy — это обёртка вокруг реального объекта. Он записывает вызовы, но делегирует их реальной реализации.
Когда использовать
| Ситуация | Пример |
|---|---|
| Хотим частично переопределить поведение | Реальная БД, но переопределили один метод |
| Хотим проверить вызовы на реальном объекте | Проверить, что логгер вызван |
Инструменты для Mocking и Stubbing
| Язык | Инструменты |
|---|---|
| Java | Mockito, EasyMock, JMockit |
| Python | unittest.mock, pytest-mock |
| Go | testify/mock, gomock |
| JavaScript | Jest, Sinon.js, Mock Service Worker |
| C# | Moq, NSubstitute, FakeItEasy |
WireMock (для тестирования API)
WireMock — это инструмент для создания заглушек HTTP API.
| Возможность | Пример |
|---|---|
| Stub | GET /payment/123 → 200 OK |
| Mock | Проверить, что API вызвал /payment с правильным телом |
| Симуляция ошибок | GET /payment/123 → 500 Internal Error |
| Симуляция задержек | GET /payment/123 → ответ через 5 секунд |
Когда использовать реальные зависимости
| Зависимость | Mock/Stub | Реальная | Почему |
|---|---|---|---|
| База данных | Модульные тесты | Интеграционные | Модульные — быстро, интеграционные — проверяют миграции |
| Внешнее API | Всегда (Stub) | Редко | Дорого, нестабильно |
| Очереди (Kafka) | Модульные тесты | Интеграционные | Проверить сериализацию, партиции |
| Файловая система | Stub | Интеграционные | Права доступа, блокировки |
| Время | Stub (фиксированное время) | Никогда | Тест должен быть детерминированным |
| Случайность | Stub (фиксированное значение) | Никогда | Тест должен быть детерминированным |
Что аналитик должен знать
Какие вопросы задать команде
| Вопрос | Зачем |
|---|---|
| “Какие зависимости заглушены в модульных тестах?” | Понимать границы тестирования |
| “Есть ли интеграционные тесты с реальными зависимостями?” | Убедиться, что заглушки соответствуют реальности |
| “Как часто обновляются заглушки при изменении API?” | Предотвратить расхождение мока и реальности |
| “Тестируются ли ошибки зависимостей?” | Таймауты, 500 ошибки, невалидные ответы |
Риски избыточного мокинга
| Риск | Объяснение |
|---|---|
| Тесты проходят, а система падает | Мок не соответствует реальному API |
| Тесты зелёные, но интеграция не работает | Не проверены форматы данных, протоколы |
| Сложно поддерживать | Каждое изменение API требует изменения моков |
Риски недостаточного мокинга
| Риск | Объяснение |
|---|---|
| Тесты медленные | Ждут реальную БД, сеть |
| Тесты нестабильные (flaky) | Внешнее API может упасть |
| Тесты дорогие | Каждый запуск — деньги (платные API) |
Пример из реального проекта
Ситуация
API интернет-магазина при создании заказа:
- Проверяет остатки в БД
- Вызывает платёжный шлюз
- Отправляет событие в Kafka
- Отправляет email через SMTP
Стратегия тестирования
| Уровень | Что заглушено | Что реальное |
|---|---|---|
| Модульные тесты | БД (in-memory), платёжный шлюз (stub), Kafka (mock), SMTP (mock) | Логика создания заказа |
| Интеграционные | Платёжный шлюз (WireMock), SMTP (Fake) | БД, Kafka |
| E2E | Ничего | Всё реальное (тестовые аккаунты) |
Что проверяют моки на модульном уровне
- Платёжный шлюз: проверить, что вызван с правильной суммой
- Kafka: проверить, что отправлено событие
order.created - SMTP: проверить, что email отправлен на правильный адрес
Распространённые ошибки
Ошибка 1: Мок слишком умный
Мок проверяет не только факт вызова, но и внутреннюю реализацию.
Пример: Мок проверяет, что метод вызван три раза, а важно только то, что вызван хотя бы раз.
Решение: Проверять поведение, а не реализацию.
Ошибка 2: Мок возвращает только успех
Нет тестов на ошибки внешних зависимостей.
Решение: Добавить тесты: “платёжный шлюз вернул ошибку → API вернул 502”.
Ошибка 3: Заглушка не соответствует реальности
Заглушка возвращает {"status": "ok"}, а реальный API — {"success": true}.
Решение: Периодически запускать интеграционные тесты с реальными API.
Ошибка 4: Избыточное мокинге
Заглушены даже простые зависимости, которые можно использовать реальные.
Решение: Для быстрых и стабильных зависимостей (время, случайность) использовать реальные.
Ошибка 5: Нет тестов на отсутствие вызова
Проверяем, что при ошибке валидации не вызывается платёжный шлюз.
Решение: Добавить мок с проверкой, что метод НЕ был вызван.
Резюме
Mocking и Stubbing — техники создания дублёров для зависимостей. Позволяют тестировать компонент в изоляции.
Stub (заглушка) — возвращает предопределённые ответы. Не проверяет вызовы. Используется, когда важен только результат.
Mock (двойник) — запоминает вызовы и проверяет их. Используется, когда важно, как и с какими параметрами вызвали зависимость.
Fake (фейк) — упрощённая рабочая реализация. Например, in-memory база данных.
WireMock — инструмент для заглушек HTTP API (для интеграционных тестов).
Когда заглушать: внешние API, медленные/дорогие/нестабильные зависимости.
Когда использовать реальные: БД в интеграционных тестах, простые зависимости.