Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Различие между Mock и Stub в контексте автоматизированного тестирования
В автоматизированном тестировании, особенно при использовании модульного тестирования (Unit Testing), часто применяются тестовые двойники (Test Doubles) для замены реальных зависимостей тестируемого компонента. Mock и Stub являются двумя ключевыми типами таких двойников, но они выполняют разные функции и используются в различных сценариях. Понимание различий критично для написания чистых, эффективных и правильно структурированных тестов.
Основное философское отличие
Различие между Mock и Stub часто объясняется через их цель в тесте:
- Stub используется для предоставления тестируемому модулю контролируемых входных данных. Его главная задача — заменить реальный объект, чтобы тест мог выполняться в изоляции, и обеспечить предопределенные ответы на вызовы методов.
- Mock используется для проверки выходных взаимодействий тестируемого модуля. Его главная задача — заменить реальный объект и затем удостовериться (verify), что тестируемый модуль вызвал его методы с ожидаемыми параметрами в ожидаемой последовательности.
Проще говоря: Stub помогает тесту запуститься (обеспечивает входные данные), а Mock проверяет, как тестируемый код взаимодействовал с внешним миром (проверяет выходные взаимодействия).
Stub: Подставка для данных
Stub — это объект с предопределенным (жестко заданным или программируемым) поведением, который заменяет реальную зависимость только для того, чтобы тест мог выполниться. Stub не содержит логики проверки взаимодействий.
Основные характеристики Stub:
- Предоставляет контролируемые ответы на вызовы методов.
- Не проверяет, как и сколько раз эти методы были вызваны.
- Его реализация часто очень простая и возвращает фиксированные значения.
- Используется для управления состоянием (state) тестируемого объекта — мы проверяем конечное состояние объекта после взаимодействия с Stub.
Пример Stub в C#:
Рассмотрим сервис, который зависит от репозитория для получения данных. В тесте мы хотим проверить логику сервиса при определенных данных из репозитория.
// Интерфейс зависимости
public interface IUserRepository
{
User GetUserById(int id);
}
// Реализация Stub для теста
public class StubUserRepository : IUserRepository
{
public User GetUserById(int id)
{
// Предопределенный ответ для определенного ID
if (id == 1)
{
return new User { Id = 1, Name = "Test User", IsActive = true };
}
// Для других ID можем возвращать null или другое предопределенное значение
return null;
}
}
// Тест, использующий Stub
[Test]
public void UserService_Should_ActivateExistingUser()
{
// Создаем Stub, который гарантированно вернет активного пользователя для ID=1
var stubRepository = new StubUserRepository();
var userService = new UserService(stubRepository);
var result = userService.ProcessUser(1);
// Проверяем СОСТОЯНИЕ: что сервис корректно обработал полученного пользователя
Assert.That(result, Is.Not.Null);
Assert.That(result.Name, Is.EqualTo("Test User"));
// Stub сам не проверял, был ли вызван GetUserById.
}
Mock: Проверка взаимодействий
Mock — это объект, который также заменяет реальную зависимость, но его основная роль заключается в регистрации вызовов своих методов, чтобы после выполнения тестового сценария можно было проверить (verify), что ожидаемые взаимодействия действительно произошли.
Основные характеристики Mock:
- Содержит ожидания (expectations) относительно того, какие методы должны быть вызваны, с какими параметрами и в каком порядке.
- После выполнения теста проводится проверка (verification) этих ожиданий.
- Часто реализуется с помощью библиотек мокинга (Moq, NSubstitute).
- Используется для проверки поведения (behavior) тестируемого объекта — мы проверяем, как объект взаимодействовал с Mock.
Пример Mock в C# с использованием библиотеки Moq:
Рассмотрим сервис, который должен вызывать метод отправки сообщения в зависимости от определенных условий.
// Интерфейс зависимости
public interface INotificationService
{
void SendEmail(string to, string body);
}
// Тест, использующий Mock
[Test]
public void OrderService_Should_SendEmail_WhenOrderIsCompleted()
{
// 1. Создаем Mock и устанавливаем ОЖИДАНИЯ
var mockNotificationService = new Mock<INotificationService>();
// Настраиваем метод. Мы ожидаем, что он будет вызван с определенными аргументами.
mockNotificationService.Setup(service => service.SendEmail("customer@email.com", "Your order is complete."));
var orderService = new OrderService(mockNotificationService.Object);
var completedOrder = new Order { Id = 123, CustomerEmail = "customer@email.com" };
// 2. Выполняем тестовое действие
orderService.FinalizeOrder(completedOrder);
// 3. ПРОВЕРЯЕМ ВЗАИМОДЕЙСТВИЕ: убеждаемся, что ожидаемый метод был вызван.
mockNotificationService.Verify(service => service.SendEmail("customer@email.com", "Your order is complete."), Times.Once());
// Тест провалится, если SendEmail не был вызван или был вызван с другими аргументами.
}
Сравнение в таблице
| Критерий | Stub | Mock |
|---|---|---|
| Основная цель | Обеспечить тест необходимыми входными данными. | Проверить выходные взаимодействия тестируемого кода. |
| Что проверяется | Состояние (State) тестируемого объекта после операции. | Поведение (Behavior) тестируемого объекта (какие вызовы были сделаны). |
| Проверка вызовов | Не проверяет, были ли методы зависимости вызваны. | Обязательно проверяет, были ли методы вызваны (с правильными аргументами и порядком). |
| Тип теста | Чаще используется в тестах на результат (возвращаемое значение или состояние). | Чаще используется в тестах на взаимодействие (collaboration tests). |
Практические рекомендации
- Используйте Stub, когда вам нужно просто подменить данные, чтобы ваш код мог выполниться, и вы хотите проверить его конечный результат или состояние.
- Используйте Mock, когда вам важно убедиться, что ваш код корректно вызвал определенный метод другой системы (например, отправку email, запись в лог, вызов внешнего API).
- Современные библиотеки (Moq, NSubstitute) часто позволяют одним объектом выполнять обе роли: вы можете настроить его как Stub (для возвращения значений), а затем проверить его как Mock. Однако концептуальное разделение остается важным для понимания сути теста.
- Чрезмерное использование Mock может привести к тестам, которые слишком тесно связаны с реализацией (проверяют не "что" делает код, а "как" он это делает), что делает их хрупкими. Stub часто дает менее связанные тесты.
Таким образом, выбор между Mock и Stub определяется целью вашего теста: проверяете ли вы конечное состояние объекта или же проверяете его коммуникацию с внешними зависимостями.