Что такое заглушка (Stub) в тестировании?

Ответ

Заглушка (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 — это твой верный солдат-тупица, который стоит на месте и по команде выдаёт заранее заготовленную бумажку с ответом. Он не умный, но он делает тесты быстрыми, стабильными и не зависящими от всей ебалы мира вокруг. И это охуенно.