К основному контенту

Mock vs Stub

Когда мы начали изучать модульное тестирование, то одними из первых терминов, с которыми пришлось познакомиться, стали Mock и Stub.

Ниже попробуем порассуждать в чем их сходство и различие, как и для чего они применяются.

Проверять работоспособность тестируемого объекта (system uder test - SUT) можно двумя способами: оценивая состояние объекта или его поведение.

В первом случае проверка правильности работы метода SUT заключается в оценке состояния самого SUT, а также взаимодействующих объектов, после вызова этого метода.

Во-втором, мы проверяем набор и порядок действий (вызовов методов взаимодействующих объектов, других методов SUT), которое должен совершить метод SUT.

Собственно, если коротко, то в одном случае используется Stub, а в другом Mock. Это объекты, которые создаются и используются взамен реальных объектов, с которым взаимодействует SUT в процессе своей работы.

Теперь подробнее.

Gerard Meszaros использует термин Test Double (дублер), как обозначение для объекта, который заменяет реальный объект, от которого зависит SUT, в тестовых целях.

Когда нам нужно использовать double?
  • Низкая скорость выполнения тестов с реальными объектами (если это, например, работа с базой, файлами, почтовым сервером и т.п.)
  • Когда есть необходимость запуска тестов независимо от окружения (например, на машине у любого разработчика)
  • Система, в которой работает код, не дает возможности (или дает, но это сложно делать) запустить код с определенным входным набором данных.
  • Нет возможности проверить, что SUT отработал правильно, например, он меняет не свое состояние, а состояние внешней системы. И там эту проверку сделать сложно.
Gerard определяет следующие типы дублеров:

  Dummy - это объекты, которые передаются в методы, но на самом деле не используются. В основном, это параметры методов (если конечно, они не влияют в тесте на то, что мы хотим проверить). Иногда это просто NULL

  Fake - это объекты, которые имеют внутреннюю реализацию, но обычно она сильно урезанная и их нельзя использовать в готовом коде.

  Stubs - обеспечивают жестко зашитый ответ на вызовы во время тестирования. Применяются для замены тех объектов, которые обеспечивают SUT входными данными. Также они могут сохранять в себе информацию о вызове (например параметры или количество этих вызовов) - такие иногда называют своим термином Test Spy. Такая "запись" позволяет оценить работу SUT, если состояние самого SUT не меняется.

  Mocks - объекты, которые настраиваются (например специфично каждому тесту) и позволяют задать ожидания в виде своего рода спецификации вызовов, которые мы планируем получить. Проверки соответствия ожиданиям проводятся через вызовы к Mock-объекту.

Из всех этих doubles только Mock'и работают на верификацию поведения. Остальные, как правило, используются для проверки состояния. В момент выполнения метода SUT использование doubles не отличается. Но Mock'и и некоторые Stub'ы требуют настроек перед запуском и позволяют оценить процесс выполнения.

Итого: если проверка результатов выполнения осуществляется через вызовы к double, то это Mock, если в ассертах мы анализируем состояние SUT, то это, скорее всего, Stub.

В классическом варианте модульного тестирования реальные объекты используются везде, где это возможно (за исключением случаев описанных выше). В мокирующем (mockist) варианте - мокируется все, кроме того объекта, который тестируем (SUT).

Какой использовать?
Плюсы и минусы есть у обоих. Остановимся на минусах.

При использовании классического подхода, когда все в тестах - это реальные объекты, в случае ошибки в одном из методов реального объекта, "красными" станут все тесты, в которых этот метод используется (независимо от того, тестируется он в них или просто используется другим объектом). Локализация проблемы может стать трудной задачей, если при написании тестов, вы не задумывались над гранулярностью теста: его минимально необходимыми зависимостями.

В обратную сторону, при мокировании всего и вся может возникнуть другая проблема: поведенческие тесты жестко фиксируют внутреннюю реализацию SUT. Что и как вызывается, с какими аргументами, какое API используется и тп. Все это создает трудности при рефакторинге и возможных изменениях в SUT - вместе с кодом все тесты придется писать заново.

Что проще, выбирать вам. Из своего опыта скажу, что классика мне ближе и понятней (радует, что и Martin Fowler того же мнения). А вот с мокированием как-то не срослось.

И, пожалуйста, не мокируйте ничего в приемочных тестах - там все должно быть в живую, иначе тесты вас обманут. Неоднократно убеждался в этом на личном опыте. Хотя, если у вас в тестах идут запросы к API внешнего сервиса типа Facebook, то тут есть над чем подумать и скорее всего их имеет смысл замокировать в части тестов.

А вообще "используйте mocks только, когда это действительно нужно"

Источники:
Mocks Aren't Stubs (Martin Fowler)
Test Double (Gerard Meszaros)

Некоторые фреймворки для мокирования (update 2020: список очень давно не обновлялся):

Интересно по теме от Roy Osherove "The Three Values of a Good Isolation Framework"

Свеженькое по теме "Using Mocks or Stubs, Revisited"

Вам может быть интересно:
"А вы не любите TDD, как не люблю его я?"

"Software-Engineering Myth Busters (покрытие кода тестами, TDD, организация и распределенные команды)"

Забавные факты:
Что вы знаете о моках? Оказывается, есть целые mock-города для тестирования автономных автомобилей. Вот про один из них.

Комментарии

  1. В том же moq эта разница не столь заметна, достаточно для Mock не вызывать Verify и он ведет себя как Stub, что вполне удобно. 

    ОтветитьУдалить

Отправка комментария

Популярные сообщения из этого блога

План "Б" или как прикольно провести субботний день

Всем привет.
Вчера состоялась конференция "План Б". Организаторами выступили ребята из Яндекса, за что им большое спасибо. Судя по приблизительным подсчетам в мероприятии участвовало около 200 человек.

Основной темой конференции было планирование, планирование всего: проектов, разработки, тестирования, дизайнеров и даже организации музыкального фестиваля.
Сначала думал написать отчет в обычном своем стиле: кто и что говорил, но почитав твиттер по #pbconf понял, что просто потеряю время :) Поэтому кому оооочень интересно узнать подробности следуйте за птичкой и вы все узнаете (тэг #pbconf попал в top-30 твиттера)
Здесь приведу лишь те вещи, которые мне запали в мозг
Роман Чернин о продуктовой разработке: "нет заказчика, нет требований, нет сроков -> как принимать решения? ответ: заводим себе Product Manager-а"
Оля Павлова (@op): "бойтесь иллюзии точной формулировки" "заказчик - ребенок, выдаем ему игрушку как можно чаще" "не забываем, …

Полезные ресурсы для молодых (и не только) тестировщиков

Уже 3 месяца провожу собеседования тестировщиков.
Поначалу они просто  веселили - после 15-летнего опыта собеседования С++-разработчиков, общение с тестировщиками (чаще были "-цы") было чем то экзотическим и забавным.

Потом становилось все грустнее и грустнее, мимими закончилось. Началась печаль.