Какие плюсы и минусы у использования стабов (stubs) в тестировании?

«Какие плюсы и минусы у использования стабов (stubs) в тестировании?» — вопрос из категории Фреймворки тестирования, который задают на 24% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Плюсы стабов:

  • Изоляция и контроль данных: Стаб заменяет реальную зависимость и возвращает заранее заданные, предсказуемые данные. Это позволяет тестировать реакцию системы на конкретные входные данные (как валидные, так и ошибочные).
  • Ускорение тестов: Как и моки, стабы исключают медленные операции (запросы к БД, внешние API).
  • Детерминированность: Поведение стаба всегда одинаково, что делает тесты стабильными и воспроизводимыми.
  • Простота создания: Стабы часто проще создать, чем моки, так как их основная задача — возвращать данные, а не отслеживать взаимодействия.
  • Тестирование граничных условий и ошибок: Легко сымитировать ситуацию, когда зависимость возвращает null, пустой список, бросает исключение или отвечает с задержкой.

Минусы стабов:

  • Не проверяют взаимодействие: В отличие от мока, стаб обычно не позволяет проверить, был ли вызван метод зависимости. Он только предоставляет данные.
  • Риск расхождения с реальностью: Если поведение реальной зависимости изменилось (например, изменился формат JSON-ответа), стаб, возвращающий старые данные, не позволит это обнаружить, что приведет к ложному прохождению unit-теста и возможному падению в продакшене.
  • Дополнительная нагрузка на поддержку: При изменении интерфейса зависимости все ее стабы в тестах требуют обновления.
  • Могут маскировать проблемы интеграции: Успешные тесты со стабами создают иллюзию работоспособности, в то время как реальное соединение между системами может быть broken.

Пример использования стаба для тестирования репозитория (Python/unittest):

# Реальный класс, который мы тестируем
class UserReportGenerator:
    def __init__(self, user_repository):
        self.user_repo = user_repository

    def generate_active_users_report(self):
        all_users = self.user_repo.get_all_users()  # Зависимость от репозитория
        active_users = [u for u in all_users if u.is_active]
        return f"Active users: {len(active_users)}"

# Тест со стабом
from unittest.mock import Mock

def test_generate_active_users_report():
    # 1. Создаем стаб репозитория
    stub_repository = Mock()

    # 2. Настраиваем стаб на возврат конкретных данных
    fake_users = [
        Mock(is_active=True, name="Alice"),
        Mock(is_active=False, name="Bob"),
        Mock(is_active=True, name="Charlie")
    ]
    stub_repository.get_all_users.return_value = fake_users

    # 3. Инжектируем стаб в тестируемый объект
    generator = UserReportGenerator(stub_repository)

    # 4. Выполняем тест и проверяем результат
    report = generator.generate_active_users_report()

    # Проверяем логику обработки данных, а не вызов метода
    assert report == "Active users: 2"
    # Примечание: мы НЕ проверяем, был ли вызван get_all_users.
    # Это был бы уже мок, а не стаб.