Как тестировать то, чего еще не существует? MOCK, STUB, FAKE — разница, применение и подводные камни

Как тестировать то, чего еще не существует? MOCK, STUB, FAKE — разница, применение и подводные камни

Представьте: проект движется вперед, разработка кипит, сроки поджимают… но вот незадача — сервисы, от которых зависит тестирование, либо не готовы, либо ведут себя непредсказуемо. В худшем случае их вообще нет.

Пауза? Нет, не вариант.

Опытные разработчики и тестировщики давно нашли способ тестировать даже то, чего еще нет. Встречайте трех героев невидимого фронта: MOCK, STUB и FAKE. Разбираемся, кто есть кто и как их правильно использовать, со Станиславом Беленовым, тестировщиком на проекте в SkillStaff.

⭐ Чек-лист в конце статьи поможет определиться с выбором заглушки для ваших задач.

Как работают заглушки

Заглушки — это специальные объекты, с помощью которых вы можете проверить программное обеспечение без готового кода, базы данных или даже сценариев, которые предусмотрены аналитикой, но невозможны к исполнению на тестовой среде.

Я использую для тестирования три вида заглушек — MOCK, STUB и FAKE. Они прекрасно имитируют работу недостающих компонентов и подходят под разные задачи, в том числе пригодятся разработчикам.

Где полезны заглушки

✔ Проверить корректность вызова методов с правильными параметрами и в нужном порядке

✔ Протестировать новые изменения, не влияя на существующий функционал.

✔ Проектировать проекты и начинать тестирование, не дожидаясь доработок других систем.

✔ Проводить автоматизированные тесты в CI/CD пайплайнах.

Разберем каждый объект подробнее.

STUB

STUB — упрощенная версия объекта, которая заменяет реальный компонент системы на время тестирования. Это минимальная версия объекта, которая просто возвращает фиксированные ответы. Стаб не выполняет логику, не отслеживает вызовы и не хранит состояние — его задача только в одном: быстро дать предсказуемый результат.

Как тестировать то, чего еще не существует? MOCK, STUB, FAKE — разница, применение и подводные камни

📌 Стабы решают несколько ключевых проблем:

1. Изоляция тестируемого кода — помогает тестировать логику, не завися от реальных сервисов.

2. Упрощение тестирования — STUB возвращает предопределенные данные, убирая сложность настройки тестов.

3. Ускорение тестов — выполняется мгновенно, без обращений к базе данных, API и другим внешним ресурсам.

4. Тестирование крайних случаев — позволяет легко эмулировать ошибки и нестандартные ответы.

Как это выглядит на практике:

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

class GetUser def get_user(self, user_id): # Логика получения пользователя из базы данных pass class GetUserStub: def get_user(self, user_id): return {"id": user_id, "name": "Test User"}

Как создать STUB

1. Определите, что подменять

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

2. Создайте реализацию

Напишите класс, который реализует этот интерфейс.

⚠ Ограничения:

STUB возвращает заранее заданный ответ, но не эмулирует сложное поведение системы. Если зависимость ведет себя нестандартно, STUB этого не покажет.

Слишком примитивные стабы могут привести к тому, что тесты перестанут отражать реальное поведение системы.

FAKE

FAKE — это объект с упрощенной, но рабочей реализацией. Он выполняет некоторые функции настоящего объекта, но не предназначен для продакшна. Например, вместо реальной базы данных можно использовать FAKE-объект, который хранит данные в памяти.

📌 Фейки решают несколько ключевых проблем:

1. Эмуляция сложного поведения — позволяет тестировать код в условиях, близких к реальным, например, при работе с базами данных или внешними API.

2. Контроль состояния — FAKE-объекты могут запоминать, какие операции с ними выполнялись, что упрощает тестирование.

3. Оптимизация тестов — снижает затраты на тестирование, так как FAKE-объекты не требуют реальных ресурсов (серверов, сетевых соединений и т. д.).

Как это выглядит на практике:

Допустим, у нас есть система, которая отправляет электронные письма. Вместо использования реального сервера, мы можем создать FAKE, который будет имитировать отправку писем и хранить их в памяти для последующей проверки.

class EmailService: def send_email(self, recipient, subject, body): # Логика отправки письма pass class FakeEmailService: def __init__(self): self.sent_emails = [] def send_email(self, recipient, subject, body): self.sent_emails.append({"recipient": recipient, "subject": subject, "body": body})

⚠ Ограничения FAKE

Если поведение фейков не соответствует реальной системе, тесты могут пропускать ошибки, которые проявятся уже в бою. К тому же FAKE-объекты зависят от внутренней реализации кода, и любое изменение может потребовать переделки тестов.

MOCK

MOCK — это вершина контроля. Мок позволяет проверять взаимодействие между компонентами. Он может не только возвращать заданные значения, как STUB, но и отслеживать, как и сколько раз его методы были вызваны.

Как тестировать то, чего еще не существует? MOCK, STUB, FAKE — разница, применение и подводные камни

📌 MOCK'и решают несколько ключевых проблем:

  1. Контроль поведения зависимостей — можно задать ожидаемое поведение объекта и проверить, как код реагирует на разные сценарии.
  2. Более точно тестировать логику обработки асинхронных операций — можно эмулировать задержки, ошибки или успешные ответы.
  3. Отсутствие зависимости от внешних ресурсов — больше никаких падений тестов из-за недоступных баз данных или API.
  4. Ускорение тестов — мокированные тесты выполняются быстрее, потому что не тратят время на запросы к внешним системам.

Как это выглядит на практике:

Предположим, у вас есть система уведомлений, которая отправляет уведомления асинхронно.

public class NotificationService { private EmailService emailService; public NotificationService(EmailService emailService) { this.emailService = emailService; } public void notifyUser Async(User user) { CompletableFuture.runAsync(() -> emailService.sendNotification(user.getEmail()));}} // Тестирование NotificationService с использованием мока public class NotificationServiceTest { @Test public void testNotifyUser Async() throws InterruptedException { EmailService mockEmailService = mock(EmailService.class); NotificationService notificationService = new NotificationService(mockEmailService); User user = new User("user@example.com"); notificationService.notifyUser Async(user); // Даем время для асинхронного вызова Thread.sleep(100); // Не рекомендуется, лучше использовать более надежные методы ожидания // Проверка, что метод sendNotification был вызван verify(mockEmailService).sendNotification("user@example.com");}}

Для создания моков можно использовать такие библиотеки как unittest.mock в Python или Mockito в Java. Для создания актуальных и полных моков необходимо четко и полно изучить аналитику, чтобы знать какие методы должны быть вызваны. Для каждого из этих методов необходимо знать возвращаемые значения.

⚠ Ограничения

  • Может давать ложное ощущение работоспособности. Если MOCK настроен неправильно, тесты могут проходить, но код в продакшене работать некорректно.
  • Высокая связанность с реализацией. Тесты с MOCK зависят от конкретного кода, и при его изменении потребуется обновлять моки.

Чек-лист. Как выбрать заглушку: STUB, MOCK или FAKE

Как тестировать то, чего еще не существует? MOCK, STUB, FAKE — разница, применение и подводные камни

Используете ли вы заглушки в своих тестах, или предпочитаете другие техники?

1
Начать дискуссию