пятница, 30 марта 2012 г.

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
- объекты, которые настраиваются (например специфично каждому тесту) и позволяют задать ожидания в виде своего рода спецификации вызовов, которые мы планируем получить

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

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

Остановимся на минусах.

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

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

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

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

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


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

Некоторые фреймворки для мокирования:
Java:     mockitojMock
.Net      Rhino Mocksmoq
C++      googlemock
Python  pyDoublesmockito-python

Интересно по теме от Roy Osherove "The Three Values of a Good Isolation Framework"
Свеженькое по теме "Using Mocks or Stubs, Revisited"


Вам может быть интересно:
"А вы не любите TDD, как не люблю его я?"
"Software-Engineering Myth Busters (покрытие кода тестами, TDD, организация и распределенные команды)"

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

1 комментарий:

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

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