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

Mock vs Stub

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

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

Проверять работоспособность тестируемого объекта (system under 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, что вполне удобно. 

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

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

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

Заметки на коленке - 3. Что еще делать, если ваши тесты уже "зеленые"?

"Lately I find I'm working on automated tests that return non-binary results. Tests that neither pass nor fail" by  @noahsussman Отличная мысль, которую я ретвитил еще в 2016. Но давайте вместе подумаем, что за этим может скрываться? ( кстати, не знаю, что при этом думал Noah ) Ваши тесты прошли и прошли "успешно". Все хорошо или все же есть, куда еще посмотреть? Дальше то, что использовал я лично и то, что еще можно прикрутить дополнительно. Естественно все шаги ниже должны быть автоматизированны. 1. Контролируйте время выполнения тестов. Если набор проверок не меняется (а такое часто бывает, к сожалению), то рост времени выполнения может говорить о проблемах в продакшен коде (чаще всего) или проблемах с окружением. 2. Контроль за количеством выполняемых тестов. "Все зеленое" не значит, что сегодня выполняли те же Х тестов, что и вчера. Смешно(нет), но случается такое, что какие-то проверки "исчезают" из запуска из-за того, что у кого-то &qu

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

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