Как стать автором
Обновить

Мок — это не костыль, мок — это спецификация

Время на прочтение2 мин
Количество просмотров4.7K
Среди множество псевдоидей, витающих вокруг ТДД, есть некоторое пренебрежение к test doubles, не в последнюю очередь связанное с их смешными названиями.

Назвали бы их как-нибудь гордо, а то мок, стаб, фейк — ощущение, что мы ничего особо не теряем, если этим не пользуемся. (В противоположность «интеграционным тестам» и «реальным зависимостям»).

Однако можно поменять точку зрения. В конце концов, мок не только и не столько подпирает зависимый компонент, сколько специфицирует поведение зависимости. А «реальная» имплементация — это некое текущее, возможно ошибочное воплощение нашей гордой идеи.

В этом смысле каждый раз, когда мы пишем

when(mockDependency.method(inputValue)).thenReturn(returnValue),

мы документируем некоторое поведение компонента.

Отсюда — несколько следствий.

Мок-объекты плотно связаны с собственно спецификациями тестов, и по вокабуляру, и по смыслу. И то, и другое фокусирует нас на требованиях и сценариях, а не на особенностях текущей имплементации.

Нам немного мешает тут видеть спецификацию тот факт, что утверждения о мокируемом компоненте в случае использования фреймворков типа Mockito — распылены по классам-клиентам, и за ними трудно уследить. В этом смысле явные классы-наследники с предопределенным поведением предпочтительнее моков типа Mockito, потому что позволяют инкапсулировать сценарии и обозревать их одним взглядом.

Таким образом, к каждому классу OrderProvider добавляются классы-сценарии ValidOrderProviderStub, ExpiredOrderProviderStub, InvalidOrderIdException_OrderProviderStub, OnlyOnceOrderProvider и так далее, которые могут находиться в одном пакете и использоваться всеми тестами сразу.

Это включает в себя Spy-классы, которые нетрудно имплементировать самому.

Кроме того, такой подход быстрее выполняется и работает без использования рефлексии.

Таким образом, при чтении кода мы можем сделать конвенцию, что наличие мока подтверждает возможность сценария, а его отсутствие запрещает наличие такого сценария. Если мы запретим клиентским классам писать mockito expectations, то в поисках NullOrderProvider выяснится, что подходящего мока не имеется, потому что null никогда не возвращается, и поэтому тестировать на этот сценарий нет смысла.

Если мок — это спецификация, то мок — отражение наших планов на компонент, и они могут быть и должны быть написаны вперед реализации.

Если мок — это спецификация, то она устроена иерархично и отражает поведение компонента в зависимости от определенных условий.

Таким образом, наши моки могут размножиться слишком сильно: безусловные моки, всегда возвращающие то же самое, а также моки с внутренними условиями, типа forIdOne_returningOne_forIdTwo_ReturningTwo_OrderProvider. Последнее перебор и следует просто создать FakeOrderProvider с соответствующим поведением, которое следует синхронизировать с реальной имплементацией.

Соответственно, если мок — это спецификация определенного поведения, то имплементация компонента — не что иное, как синхронизация поведения компонента с его моками.

И это, пожалуй, главный аргумент против моков — необходимость синхронизировать их поведение с текущей реальной имплементацией, об этом поговорим отдельно в следующий раз.
Теги:
Хабы:
Всего голосов 14: ↑12 и ↓2+10
Комментарии3

Публикации

Ближайшие события