Если вы только издеваетесь над типами, которыми владеете?

Я прочитал TDD: только макет типов, которыми вы владеете , Марк Нидхэм и хотел бы знать, является ли это лучшей практикой или нет?

Пожалуйста, обратите внимание, что он не против насмешек, но против насмешек прямо – он говорит, что пишет обертку и насмехается, что все в порядке.

Зависит ли вы от макета или mock ™ …

Учитывая, что вы просто используете макетную фреймворк (например, Mockito) для создания заглушек, тогда создание заглушек типов, которыми вы не владеете, полностью в порядке и разумно.

Если, однако, вы используете макетную фреймворк (например, Mockito) для создания объектов mock ™, тогда вы лучше всего следуете советам евангелистов-макетов. Лично я потерял связь с этим движением, поэтому я не могу сказать вам, следует ли считать совет Марка Нидхэма кошерным или нет.

Ирония в сторону, что Марк пишет об издевательстве EntityManagers в Hibernate, звучит разумно сам по себе. Но я сомневаюсь, что мы можем обобщить правило, подобное «никогда не издевающимся типам, которые вы не владеете» из этого конкретного случая. Иногда это может иметь смысл, иногда нет.

Мой ответ «нет». Вы должны издеваться над тем, что имеет смысл в контексте данного модульного теста. Не имеет значения, имеете ли вы «собственный» издевавшийся тип или нет.

В наши дни в среде Java или .NET все (и я действительно имею в виду все) можно легко высмеять. Таким образом, нет никаких технических причин, чтобы идти наперебой, прежде чем писать дополнительный код обертки.


Некоторые дополнительные идеи, о которых я думал недавно (ноябрь 2010 г.), которые показывают, насколько нелогичны «только макетные типы, которыми вы владеете», могут быть:

  1. Предположим, вы создаете оболочку для стороннего API, а затем вы издеваетесь над оберткой в ​​модульных тестах. Позже, однако, вы видите, что shell может быть повторно использована в другом приложении, поэтому вы перемещаете ее в отдельную библиотеку. Итак, теперь shell больше не «принадлежит» вам (поскольку она используется в нескольких приложениях, которые могут поддерживаться разными командами). Должны ли разработчики создать новую оболочку для старой?!? И продолжайте делать это рекурсивно, добавив слой на слой практически бесполезного кода?
  2. Предположим, что кто-то еще создал хороший обертку для некоторого нетривиального API и сделал его доступным как библиотека многократного использования. Если указанная shell – это то, что мне нужно для моего конкретного случая использования, должен ли я сначала создать оболочку для обертки с почти идентичным API, так что я буду «владеть» этим?!?

Для конкретного и реалистичного примера рассмотрим API Apache Commons Email, который является не чем иным, как оболочкой для стандартного API Java Mail. Поскольку я не владею им, должен ли я всегда создавать оболочку для Commons Email API, когда я пишу модульные тесты для classа, который должен отправлять электронную почту?

Мне нравится объяснение, которое проект Mockito дает на этот вопрос.

Не издевайтесь над типом, которым вы не владеете!

Это не сложная линия, но переход этой линии может иметь последствия! (это, скорее всего, будет)

  1. Представьте код, который издевается над третьей стороной lib. После определенного обновления третьей библиотеки логика может немного измениться, но набор тестов будет выполняться просто отлично, потому что это насмехается. Так что позже, думая, что все хорошо, строительная стена в конце концов зеленая, программное обеспечение развернуто и … Boom
  2. Это может быть признаком того, что текущий дизайн не отделен от этой сторонней библиотеки.
  3. Еще одна проблема заключается в том, что сторонняя библиотека может быть сложной и требует много макетов даже для правильной работы. Это приводит к чрезмерно определенным испытаниям и сложным светильникам, что само по себе компрометирует компактную и читаемую цель. Или к тестам, которые недостаточно покрывают код, из-за сложности издеваться над внешней системой.

Вместо этого наиболее распространенным способом является создание оберток вокруг внешней библиотеки lib / system, хотя следует помнить о риске утечки абстракции, где слишком много API, концепций или исключений низкого уровня выходит за пределы оболочки. Чтобы проверить интеграцию с сторонней библиотекой, запишите тесты интеграции и сделайте их как можно более компактными и читаемыми.

Я собирался сказать «нет», но, быстро взглянув на сообщение в блоге, я вижу, о чем он говорит.

Он говорит конкретно о насмешливых EntityManager в Hibernate. Я против этого. EntityManager должен быть скрыт внутри DAO (или аналогичных), и DAO – это то, что должно быть издевательством. Тестирование вызовов по одной строке EntityManager – полная трата вашего времени и будет ломаться, как только что-то изменится.

Но если у вас есть сторонний код, который вы хотите проверить, как вы с ним взаимодействуете, во что бы то ни стало.

Я, конечно, из меньшинства, но я считаю, что он насмехается как запах кода и, если это возможно, использует инъекцию зависимостей. Обоснование заключается в том, что издевательство – это, в основном, обходной путь для тестирования некоторых сложных тестов. Mocks ослабляет тесты, потому что они ведут себя (в лучшем случае), как определенная версия библиотеки. Если библиотека изменится, ваш тест потеряет все, что проверяет значение.

Вы можете видеть, что читаете выше, что я использую собственные аргументы Марка Нидхэма, но не для того, чтобы сказать, что вы не должны издеваться над объектом, которого у вас нет, но что вы не должны макетировать вообще …

Хорошо, если инъекция зависимостей не является опцией, тогда давайте макет … но тогда вы должны понимать, что ваш тест является подделкой и не будет вести себя как производственный код. Это не настоящий модульный тест, а лишь частично поддельный. Если возможно, вы можете сделать это меньше, добавив тесты, которые проверяют, что поведение соответствует ожидаемым для Mocked Objects.

ИМХО, вопрос владения не имеет значения.

Соответствующий вопрос относится к сочетанию , т. Е. Что указывает ваш тестовый код. Вы, конечно, не хотите, чтобы тестовый код указывал детали API некоторой библиотеки, которую вы использовали. Это то, что вы получаете, когда вы, например, используете Mockito, чтобы издеваться над библиотекой непосредственно в вашем тестовом classе.

Широкое предложение по решению этой проблемы – создать обертку вокруг библиотеки, а затем обмануть оболочку. Но это имеет следующие недостатки:

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

Поэтому вместо этого я бы рекомендовал полностью отделить тесты от интерфейсов в продуктивном коде . Не помещайте mocks в тестовый код напрямую, но создайте отдельный class заглушки, который реализует или издевается над продуктивным интерфейсом. Затем добавьте второй интерфейс к заглушке, который позволяет тестам выполнять необходимую настройку или утверждения. Затем вам нужно только адаптировать один class в случае изменения производительного интерфейса, и вы даже можете позволить себе издеваться / заглушить интерфейс библиотеки, которая является сложной или часто меняет.


Примечание. Все это предполагает, что на самом деле необходимо использовать макет или заглушку. Я не обсуждал этот вопрос здесь, потому что это не касалось вопроса ОП. Но действительно спросите себя, нужно ли вообще использовать макеты / заглушки . По моему опыту, они злоупотребляют …

Я согласен с тем, что Марк говорит. Вы, к сожалению, не можете издеваться над всем, и есть что-то, чего вы не хотите издеваться, просто потому, что его обычное использование – черный ящик.

Мое эмпирическое правило – это макетные вещи, которые сделают тест быстрым, но не сделают тест шелушащимся. Помните, что не все подделки одинаковы, а Mocks – это не заглушки .

Давайте будем гением компьютера.