Ответ
Заглушка (Stub) — это тестовый дублёр (test double), который заменяет реальную зависимость в тестируемой системе, предоставляя предопределённые, фиксированные ответы на вызовы своих методов. Её основная цель — изолировать тестируемый код от внешних сервисов или сложной логики.
Отличие Stub от Mock:
| Аспект | Stub (Заглушка) | Mock (Макет) |
|---|---|---|
| Цель | Предоставить данные для теста. | Проверить взаимодействие (какие методы вызывались, с какими аргументами). |
| Проверка | Не проверяет, как её использовали. Проверяется состояние тестируемой системы. | Проверяет, что ожидаемые вызовы были совершены. |
| Фокус | Состояние (State). | Поведение (Behavior). |
Практический пример:
Допустим, мы тестируем сервис PriceCalculator, который зависит от IDiscountService.
// Интерфейс зависимости
public interface IDiscountService
{
decimal GetDiscount(string userId);
}
// Реальная реализация (не используется в тесте)
public class RealDiscountService : IDiscountService
{
public decimal GetDiscount(string userId)
{
// Сложная логика: запрос к БД, вызов внешнего API и т.д.
return FetchDiscountFromDatabase(userId);
}
}
// Stub для теста
public class FixedDiscountStub : IDiscountService
{
private readonly decimal _fixedDiscount;
public FixedDiscountStub(decimal discount) => _fixedDiscount = discount;
public decimal GetDiscount(string userId)
{
// Игнорируем userId, всегда возвращаем фиксированную скидку
return _fixedDiscount;
}
}
// Тест с использованием Stub
[Test]
public void CalculatePrice_WithDiscount_AppliesCorrectly()
{
// Arrange
var discountStub = new FixedDiscountStub(10.0m); // Заглушка всегда возвращает 10%
var calculator = new PriceCalculator(discountStub); // Внедряем заглушку
decimal basePrice = 100.0m;
// Act
decimal finalPrice = calculator.CalculateFinalPrice("user123", basePrice);
// Assert
// Проверяем РЕЗУЛЬТАТ работы тестируемого метода, а не то, как вызвали заглушку.
Assert.AreEqual(90.0m, finalPrice); // 100 - 10%
}
Когда использовать Stub?
- Когда зависимость возвращает данные, необходимые для выполнения теста (например, конфигурация, курс валют, данные пользователя).
- Когда нужно симулировать определённое состояние внешней системы (например, «сервис недоступен» — заглушка бросает исключение).
- Когда реальная зависимость медленная, недетерминированная или её сложно настроить для теста (БД, внешние API).
Главный принцип: Stub делает тест стабильным, быстрым и изолированным, подменяя мир вокруг тестируемого объекта контролируемыми условиями.
Ответ 18+ 🔞
Давай разжую тебе про заглушки, а то я вижу, в глазах туман. Представь, ты тестируешь свой новый шикарный калькулятор цен, а он, сука, лезет в базу данных за скидкой для пользователя. И вот уже не юнит-тест, а целое интеграционное приключение с поднятием контейнеров и прочей хуйнёй. Нахер оно надо?
Вот тут-то и выходит на сцену заглушка (Stub). Это как дублёр в кино, только для твоего кода. Ты подсовываешь тестируемому объекту не реальную, ебанутую зависимость, которая лезет в сеть или в БД, а свою подставную крысу. Эта крыса не делает нихуя полезного, она просто тупо возвращает те данные, которые ты ей приказал вернуть. Всё.
Stub vs Mock: в чём разница, ёпта?
Люди постоянно это путают, а разница — как между молотком и отвёрткой. Оба инструменты, но для разного.
| Штука | Stub (Заглушка) | Mock (Макет) |
|---|---|---|
| Зачем? | Дать тесту нужные данные. | Проверить, что тестируемый объект правильно общался с зависимостью. |
| Что проверяем? | Состояние системы после работы. Был ли результат правильным? | Поведение во время работы. А вызывал ли он метод Save()? А с теми ли аргументами? |
| Суть | Тупой ответчик. "На, получи свои 10% скидки и отъебись". | Строгий инспектор. "Так-так, я записываю. Ты вызвал мой метод GetDiscount ровно один раз? А userId передал правильный?" |
Пример из жизни, чтобы вообще всё стало ясно
Допустим, есть у нас интерфейс для сервиса скидок. Реальная реализация — это пиздец: стучится в удалённый микросервис, который ещё и падает каждые полчаса.
// Вот эта наша зависимость, от которой хотим избавиться в тесте
public interface IDiscountService
{
decimal GetDiscount(string userId);
}
Нам для теста калькулятора не нужно, чтобы он реально куда-то ходил. Нам нужно просто проверить логику: "если скидка 10%, то от цены 100 должно получиться 90". Вот и делаем тупую, как валенок, заглушку:
// Заглушка. Никакой логики, только фиксированный ответ.
public class FixedDiscountStub : IDiscountService
{
private readonly decimal _fixedDiscount;
public FixedDiscountStub(decimal discount) => _fixedDiscount = discount;
public decimal GetDiscount(string userId)
{
// Нас вообще не ебёт, какой userId пришёл. Возвращаем то, что сказали при создании.
return _fixedDiscount;
}
}
И сам тест теперь — красота, изоляция полная:
[Test]
public void CalculatePrice_WithDiscount_AppliesCorrectly()
{
// Arrange (Готовим)
var discountStub = new FixedDiscountStub(10.0m); // Говорим заглушке: "Возвращай всегда 10%"
var calculator = new PriceCalculator(discountStub); // Пихаем заглушку в калькулятор
decimal basePrice = 100.0m;
// Act (Действуем)
decimal finalPrice = calculator.CalculateFinalPrice("user123", basePrice);
// Assert (Проверяем)
// Проверяем РЕЗУЛЬТАТ, а не то, как дергали заглушку. Это важно!
Assert.AreEqual(90.0m, finalPrice); // 100 - 10% = 90. Всё, тест прошёл.
}
Так когда эту заглушку впихивать?
- Когда зависимость — тормоз. База данных, внешнее API, файловая система. Заглушка отработает за наносекунды.
- Когда нужно конкретное состояние. "А что, если сервис скидок вернёт 0?" или "А если кинет исключение
ServiceUnavailableException?". Заглушка сделает это, не напрягая реальные системы. - Когда просто нужны данные для сценария. Дай мне пользователя-админа, дай конфиг с такими-то настройками. Заглушка — идеальный поставщик фиктивных данных.
Короче, Stub — это твой верный солдат-тупица, который стоит на месте и по команде выдаёт заранее заготовленную бумажку с ответом. Он не умный, но он делает тесты быстрыми, стабильными и не зависящими от всей ебалы мира вокруг. И это охуенно.