Ответ
Mock и Stub — это виды тестовых заглушек (test doubles), используемых для изоляции тестируемого кода от его зависимостей. Основное различие — в их цели и способе взаимодействия с тестом.
Stub (Заглушка)
- Цель: Предоставить тестируемому коду контролируемые входные данные, чтобы протестировать его поведение при разных условиях. Stub заменяет реальную зависимость, возвращая заранее заданные, «жестко закодированные» ответы.
- Взаимодействие: Тест не проверяет, как тестируемый код взаимодействовал с заглушкой. Важен только конечный результат работы тестируемого кода.
- Аналогия: Короткий опросник с заранее известными ответами (A, B, C). Вы проверяете, как система обрабатывает эти ответы, но не следите за самим процессом заполнения.
Пример Stub (используя Moq):
// Интерфейс зависимости
public interface IDiscountService
{
decimal GetDiscount(string customerCode);
}
// Тест с использованием Stub
[Test]
public void CalculateTotal_WithPremiumCustomer_AppliesDiscount()
{
// 1. ARRANGE: Создаем Stub
var discountStub = new Mock<IDiscountService>();
// Настраиваем Stub всегда возвращать скидку 10% для любого кода
discountStub.Setup(s => s.GetDiscount(It.IsAny<string>())).Returns(0.10m);
var calculator = new OrderCalculator(discountStub.Object);
var order = new Order { Total = 100.00m, CustomerCode = "PREMIUM123" };
// 2. ACT: Вызываем тестируемый метод
var finalTotal = calculator.CalculateTotal(order);
// 3. ASSERT: Проверяем РЕЗУЛЬТАТ работы тестируемого кода
Assert.AreEqual(90.00m, finalTotal); // Ожидаем 100 - 10%
// НЕТ проверок на то, как вызывался discountStub
}
Mock (Макет)
- Цель: Проверить взаимодействие (behavior) тестируемого кода с зависимостью. Mock заменяет зависимость и записывает, какие методы были вызваны, с какими параметрами и сколько раз.
- Взаимодействие: Тест явно проверяет (verifies), что ожидаемые вызовы к mock-объекту действительно произошли. Это проверка процесса, а не только результата.
- Аналогия: Регистратор нажатий клавиш. Вы проверяете, что пользователь нажал именно клавиши 'Ctrl+S', а не просто смотрите на сохраненный файл.
Пример Mock (используя Moq):
public interface IOrderRepository
{
void Save(Order order);
}
[Test]
public void ProcessOrder_ValidOrder_SavesToRepository()
{
// 1. ARRANGE: Создаем Mock
var repositoryMock = new Mock<IOrderRepository>();
// Настраиваем mock, но возвращать нечего (void метод)
var processor = new OrderProcessor(repositoryMock.Object);
var order = new Order { Id = 1, Total = 50.00m };
// 2. ACT
processor.ProcessOrder(order);
// 3. ASSERT: Проверяем ВЗАИМОДЕЙСТВИЕ (был ли вызван Save с правильным order)
repositoryMock.Verify(r => r.Save(order), Times.Once); // КРИТИЧНАЯ ПРОВЕРКА MOCK
// Также можно проверить результат, если он есть
}
Итог:
- Используйте Stub, когда вам нужно задать состояние (подменить данные) для теста.
- Используйте Mock, когда вам нужно проверить поведение (удостовериться, что определенный метод был вызван).
На практике библиотеки вроде Moq или NSubstitute позволяют создавать объекты, которые могут быть и stub, и mock одновременно. Различие становится понятием в голове тестировщика: «Я использую этот объект как stub, чтобы подставить данные» или «Я использую его как mock и буду проверять вызовы».