Ответ
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 и буду проверять вызовы».
Ответ 18+ 🔞
Давай разберёмся с этими вашими заглушками, а то народ путается, как последние идиоты. Mock и Stub — это как два разных инструмента в ящике: один молоток, другой отвёртка. И если ты пытаешься шуруп забить молотком, получается пиздец, а не ремонт.
Stub — это тупая заглушка, которая даёт готовые ответы.
Её задача — подсунуть тестируемому коду нужные данные, чтобы проверить, как он с ними справится. Всё, что происходит внутри заглушки, тебя не ебёт. Главное — результат.
Вот смотри, как это выглядит на деле. Допустим, у нас есть сервис скидок, который в реальности лезет в базу, но нам это нахуй не нужно:
public interface IDiscountService
{
decimal GetDiscount(string customerCode);
}
[Test]
public void CalculateTotal_WithPremiumCustomer_AppliesDiscount()
{
// Делаем заглушку
var discountStub = new Mock<IDiscountService>();
// Говорим: "Вот тебе скидка 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" };
// Дёргаем метод
var finalTotal = calculator.CalculateTotal(order);
// Смотрим: получилось ли 90 рублей? Если да — красава.
Assert.AreEqual(90.00m, finalTotal);
// А как там вызывался GetDiscount — нам похуй. Это Stub.
}
Mock — это стукач, который следит за вызовами.
Его задача — зафиксировать, что тестируемый код правильно общался с зависимостью. Вызвал метод? С правильными аргументами? Ровно один раз? Вот это всё проверяет Mock.
Пример, чтобы стало совсем понятно. Допустим, у нас репозиторий заказов, который должен их сохранять:
public interface IOrderRepository
{
void Save(Order order);
}
[Test]
public void ProcessOrder_ValidOrder_SavesToRepository()
{
// Делаем мок
var repositoryMock = new Mock<IOrderRepository>();
// Save — void метод, настраивать нечего
var processor = new OrderProcessor(repositoryMock.Object);
var order = new Order { Id = 1, Total = 50.00m };
// Запускаем обработку
processor.ProcessOrder(order);
// А вот теперь самое важное: проверяем, был ли вызов Save
repositoryMock.Verify(r => r.Save(order), Times.Once);
// Если не был — тест провален, потому что логика ебнутая.
}
Короче, запомни раз и навсегда:
- Stub — это когда тебе подсовывают данные. Ты проверяешь результат.
- Mock — это когда ты проверяешь общение. Ты убеждаешься, что нужный метод вызвался.
На практике такие библиотеки, как Moq, позволяют делать из одного объекта и то, и другое. Но в голове держи чёткое разделение: сегодня ты используешь его как тупой источник данных (Stub), а завтра — как доносчика (Mock). Главное — не перепутай, а то будет тебе хиросима, а не unit-тест.