Как избежать дублирования логики с помощью Mocks

У меня есть следующая задача, и я не нашел хорошего ответа. Я использую среду Mocking (в данном случае JMock), чтобы позволить модульным тестам быть изолированными от кода базы данных. Я издеваюсь над доступом к классам, которые включают логику базы данных, и отдельно тестирую классы базы данных с помощью DBUnit.

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

А что, если в будущем это поведение нужно будет изменить, а возврат null больше не будет уместным, скажем, из-за того, что состояние стало более сложным, поэтому мне нужно будет вернуть объект, который сообщает, что значение не существует, и какой-то дополнительный факт из базу данных.

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

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

Какие-либо предложения?

ОБНОВИТЬ:

Попробую уточнить свой вопрос. Я думаю о том, когда код будет развиваться с течением времени, как убедиться, что интеграция не прерывается между классами, протестированными с помощью макета, и фактической реализацией класса, который представляет макет.

Например, у меня только что был случай, когда у меня был метод, который был изначально создан и не ожидал нулевых значений, поэтому это не был тест на реальном объекте. Затем пользователь класса (протестированный с помощью макета) был расширен, чтобы передавать значение null в качестве параметра при определенных обстоятельствах. При интеграции, которая сломалась, потому что настоящий класс не был проверен на нуль. Теперь при создании этих классов сначала это не имеет большого значения, потому что вы тестируете оба конца по мере создания, но если дизайн должен развиваться два месяца спустя, когда вы склонны забывать о деталях, как вы будете тестировать взаимодействие между эти два набора объектов (тот, который тестировался с помощью макета по сравнению с фактической реализацией)?

Основная проблема, по-видимому, заключается в дублировании (то есть нарушении принципа DRY), ожидания действительно хранятся в двух местах, хотя связь концептуальна, фактического дублирующего кода нет.

[Редактировать после второго редактирования ответа Аарона Дигуллы]:

Правильно, именно этим я и занимаюсь (за исключением того, что есть некоторое дальнейшее взаимодействие с БД в классе, который тестируется через DBUnit и взаимодействует с базой данных во время ее тестов, но это та же идея). Итак, теперь предположим, что нам нужно изменить поведение базы данных, чтобы результаты были другими. Тест с использованием мока будет продолжать проходить, если 1) кто-то не вспомнит или 2) он не сломается при интеграции. Таким образом, возвращаемые значения хранимой процедуры (скажем) базы данных по существу дублируются в тестовых данных макета. Что меня беспокоит в дублировании, так это то, что дублируется логика, и это тонкое нарушение DRY. Может быть, это так и есть (в конце концов, есть причина для интеграционных тестов), но я чувствовал, что вместо этого я что-то упускаю.

[Редактировать при запуске награды]

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

[Редактировать присуждение награды]

Спасибо всем, кто потратил время, отвечая на вопрос. Победитель научил меня чему-то новому о том, как думать о передаче данных между двумя слоями, и первым нашел ответ.


person Yishai    schedule 13.03.2009    source источник
comment
Ваш вопрос немного сбивает с толку. Вы можете отредактировать его.   -  person Aaron Digulla    schedule 13.03.2009


Ответы (11)


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

person Nat    schedule 12.05.2009
comment
Это отличное предложение — абстрагировать представление данных таким образом, чтобы совместить фиктивное и реальное построение данных класса. Я должен попробовать это, чтобы увидеть, насколько большую часть проблемы это решает. - person Yishai; 12.05.2009

Вы принципиально требуете невозможного. Вы просите, чтобы ваши модульные тесты предсказывали и уведомляли вас, когда вы меняете поведение внешнего ресурса. Как они могут узнать, не написав тест для создания нового поведения?

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

Макет не «плохо себя ведет», как вы прокомментировали. Макет делает именно то, для чего вы его настроили. Тот факт, что спецификация изменилась, не имеет значения для макета. Единственная проблема в этом сценарии заключается в том, что человек, внедривший изменение, забыл обновить модульные тесты. На самом деле я не совсем понимаю, почему вы думаете, что происходит какое-то дублирование проблем.

Кодировщик, который добавляет в систему какой-то новый возвращаемый результат, отвечает за добавление модульного теста для обработки этого случая. Если этот код также на 100 % уверен, что теперь никоим образом не может быть возвращен нулевой результат, тогда он также может удалить старый модульный тест. Но зачем тебе? Модульный тест правильно описывает поведение тестируемого объекта, когда он получает нулевой результат. Что произойдет, если вы измените серверную часть вашей системы на какую-то новую базу данных, которая возвращает нуль? Что, если спецификация снова вернется к возврату null? С таким же успехом вы можете оставить тест, так как ваш объект действительно может получить что-либо из внешнего ресурса, и он должен изящно обрабатывать все возможные случаи.

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

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

person womp    schedule 08.05.2009
comment
Спасибо за подробный ответ, и вы действительно можете быть правы, что я прошу о невозможном, или, говоря более формально, для этого и нужны интеграционные тесты. Но суть моего вопроса не в том, что некоторые внешние ресурсы меняются, а в том, что какая-то часть дизайна меняется (то есть развивается) из-за рефакторинга, а та часть, которая была смоделирована, была слишком тяжелой для модульного теста, поэтому она не могла просто все тестируются вместе. - person Yishai; 08.05.2009
comment
Когда вы пишете Вся цель насмешек состоит в том, чтобы отделить ваши тесты от реальных ресурсов, мой вопрос исходит из точки зрения насмешек, что вы издеваетесь только над типами, которыми владеете mockobjects.com/2008/11/only-mock-types-you-own-revisited.html - person Yishai; 08.05.2009
comment
Я согласен с этим точно, но я считаю, что вы концептуально пытаетесь создать ссылку на свои макеты, что прямо противоположно тому, для чего они предназначены;) - person womp; 09.05.2009

Вы ничего не упускаете здесь. Это недостаток модульного тестирования с фиктивными объектами. Похоже, вы правильно разбиваете свои модульные тесты на блоки разумного размера. Это хорошая вещь; гораздо чаще люди слишком много тестируют в «модульном» тесте.

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

Иногда целесообразно использовать настоящего соавтора вместо имитации в модульном тесте. Например, если вы выполняете модульное тестирование объекта доступа к данным, использование реального объекта домена в модульном тесте вместо макета часто достаточно просто настроить, и он работает так же хорошо. Обратное часто неверно — объекты доступа к данным обычно нуждаются в соединении с базой данных, файловом или сетевом соединении и довольно сложны и требуют много времени для настройки; использование реального объекта данных при модульном тестировании объекта домена превратит модульный тест, который занимает микросекунды, в тест, который занимает сотни или тысячи миллисекунд.

Итак, подведем итог:

  1. Напишите интеграционное/функциональное тестирование, чтобы выявить проблемы с взаимодействующими объектами.
  2. Не всегда нужно издеваться над соавторами — используйте здравый смысл.
person Don McCaughey    schedule 08.05.2009

Модульные тесты не могут сказать вам, когда метод внезапно имеет меньший набор возможных результатов. Для этого и предназначено покрытие кода: оно сообщит вам, что код больше не выполняется. Это, в свою очередь, приведет к обнаружению мертвого кода на прикладном уровне.

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

[EDIT2] Насмешка над базой данных означает, что вам все равно, работает ли драйвер БД. Что вы хотите знать, так это то, может ли ваш код правильно интерпретировать данные, возвращаемые БД. Кроме того, это единственный способ проверить, правильно ли работает ваша обработка ошибок, потому что вы не можете сказать реальному драйверу БД, когда вы видите этот SQL, выдать эту ошибку. Это возможно только с макетом.

Согласен, нужно время, чтобы привыкнуть. Вот что я делаю:

  • У меня есть тесты, которые проверяют, работает ли SQL. Каждый SQL выполняется один раз для статической тестовой БД, и я проверяю, что возвращаемые данные соответствуют моим ожиданиям.

  • Все остальные тесты выполняются с фиктивным коннектором БД, который возвращает предопределенные результаты. Мне нравится получать эти результаты, запуская код в базе данных, где-то регистрируя первичные ключи. Затем я пишу инструмент, который берет эти первичные ключи и выгружает код Java с макетом в System.out. Таким образом, я могу очень быстро создавать новые тестовые случаи, и тестовые примеры будут отражать правду.

    Более того, я могу воссоздать старые тесты (при изменении БД), снова запустив старые идентификаторы и мой инструмент.

person Aaron Digulla    schedule 13.03.2009
comment
Спасибо за ответ. Насколько я вижу, весь код подпадает под изменение. Будет какой-то небольшой непокрытый код, где реальный класс будет внедрен вместо макета, но это не изменится. Макет ведет себя неправильно, потому что он создает ожидания, которые реальный класс больше не соответствует. - person Yishai; 13.03.2009
comment
Спасибо за правку, но я не понимаю. Если я имитирую вызов базы данных, макет должен возвращать тестовые данные. Как это может не повлиять на результат теста? - person Yishai; 25.03.2009

Я хотел бы сузить проблему до сути.

Эта проблема

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

Когда вы пишете код, вы используете свои методы несколько раз. Вы получаете отношение 1:n между определением метода и его использованием. Каждый класс, использующий этот метод, будет использовать его макет в соответствующем тесте. Таким образом, макет также используется n раз.

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

Вы запускаете свои тесты - все проходят.

Но со временем вы что-то забыли... макет никогда не возвращает null. Таким образом, n тест для n классов, использующих макет, не тестируется для null.

Ваш QA не пройдет, хотя ваши тесты не провалились.

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

Решение

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

Трудно добавить это позже. - Так что начните пораньше и будьте обширны со своими тестами.

Как упоминалось другими пользователями, покрытие кода выявляет некоторые непроверенные случаи. Но отсутствующий код обработки ошибок и отсутствующий тест соответствия не будут отображаться в покрытии кода. (100% покрытие кода не означает, что вы ничего не упускаете.)

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

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

Поскольку каждый вызывающий класс обрабатывает ошибки или null по-разному, это не дублирующий код, которого можно было бы избежать. Разное лечение требует разных тестов.


Совет. Держите макет простым и понятным. Переместите ожидаемые возвращаемые значения в метод тестирования. (Ваш макет может передать их просто обратно.) Избегайте проверки решений в макетах.

person MrFox    schedule 09.05.2009
comment
Спасибо за Ваш ответ. Проблема не в том, что класс, использующий макет, не проверял значение null. Вполне может быть, но ожидаемые результаты при получении нуля изменились. - person Yishai; 10.05.2009

Вот как я понимаю ваш вопрос:

Вы используете фиктивные объекты своих сущностей для тестирования бизнес-уровня вашего приложения с помощью JMock. Вы также тестируете свой уровень DAO (интерфейс между вашим приложением и вашей базой данных) с помощью DBUnit и передаете реальные копии ваших сущностных объектов, заполненных известным набором значений. Поскольку вы используете 2 разных метода подготовки тестовых объектов, ваш код нарушает DRY, и вы рискуете рассинхронизировать тесты с реальностью по мере изменения кода.

Фолвер говорит...

Это не совсем то же самое, но это, безусловно, напоминает мне статью Мартина Фаулера Моки не заглушки . Я рассматриваю маршрут JMock как фиктивный способ, а маршрут «настоящие объекты» — как классический способ выполнения тестирования.

Один из способов быть максимально СУХИМ при решении этой проблемы — быть скорее классиком, чем насмешником. Может быть, вы можете скомпрометировать и использовать в своих тестах настоящие копии ваших bean-объектов.

Создатели пользователей во избежание дублирования

Что мы сделали в одном проекте, так это создали Makers для каждого из наших бизнес-объектов. Создатель содержит статические методы, которые будут создавать копию данного объекта сущности, заполненную известными значениями. Затем, какой бы тип объекта вам ни понадобился, вы можете вызвать создателя этого объекта и получить его копию с известными значениями для использования в тестировании. Если у этого объекта есть дочерние объекты, ваш создатель вызовет создателей для дочерних объектов, чтобы построить его сверху вниз, и вы получите столько полного графа объектов, сколько вам нужно. Вы можете использовать эти объекты maker для всех своих тестов — передавая их в БД при тестировании вашего уровня DAO, а также передавая их вашим служебным вызовам при тестировании ваших бизнес-сервисов. Поскольку производители многоразовые, это довольно СУХОЙ подход.

Одна вещь, для которой вам по-прежнему нужно использовать JMock, — это макет вашего уровня DAO при тестировании вашего сервисного уровня. Если ваша служба обращается к DAO, вы должны убедиться, что вместо этого в нее вводится макет. Но вы по-прежнему можете использовать своих создателей точно так же — при настройке ваших ожиданий просто убедитесь, что ваш фиктивный DAO возвращает ожидаемый результат, используя Maker для соответствующего объекта сущности. Таким образом, мы по-прежнему не нарушаем DRY.

Хорошо написанные тесты уведомят вас об изменении кода

Мой последний совет, чтобы избежать проблем с изменением кода с течением времени, заключается в том, чтобы всегда иметь тест, который обрабатывает пустые входные данные. Предположим, что при первом создании метода нули неприемлемы. У вас должен быть тест, который проверяет, что исключение генерируется, если используется null. Если позже пустые значения станут приемлемыми, код вашего приложения может измениться, чтобы нулевые значения обрабатывались по-новому, и исключение больше не вызывалось. Когда это произойдет, ваш тест начнет давать сбои, и вы будете предупреждены о том, что все не синхронизировано.

person Justin Standard    schedule 12.05.2009

Вам просто нужно решить, является ли возврат null предполагаемой частью внешнего API или это деталь реализации.

Модульные тесты не должны заботиться о деталях реализации.

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

Имеет ли смысл внешний POV, что эта вещь возвращает NULL, или это удобное следствие, потому что клиент может сделать прямые предположения относительно значения этого NULL? NULL должен означать void/nix/nada/unavailable без какого-либо другого значения.

Если вы планируете детализировать это условие позже, вам следует обернуть проверку NULL во что-то, что возвращает информативное исключение, перечисление или логическое значение с явным именем.

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

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

person Tormod    schedule 13.05.2009

Для конкретного сценария вы меняете возвращаемый тип метода, который будет перехватываться во время компиляции. Если бы этого не было, это появилось бы в покрытии кода (как упоминал Аарон). Даже в этом случае у вас должны быть автоматизированные функциональные тесты, которые будут запускаться вскоре после регистрации. Тем не менее, я делаю автоматические дымовые тесты, так что в моем случае они это поймут :).

Не задумываясь над вышеизложенным, у вас все еще есть 2 важных фактора, играющих в исходном сценарии. Вы хотите уделить коду модульного тестирования такое же внимание, как и остальному коду, а это значит, что разумно хотеть сохранить его СУХИМ. Если бы вы занимались TDD, это в первую очередь подтолкнуло бы эту проблему к вашему дизайну. Если вы не в этом, другой вовлеченный противоположный фактор - YAGNI, вы не хотите получать каждый (не)вероятный сценарий в своем коде. Итак, для меня это было бы так: если мои тесты говорят мне, что я что-то упускаю, я дважды проверяю тест в порядке и продолжаю вносить изменения. Я стараюсь не делать сценариев «что если» с моими тестами, так как это ловушка.

person eglasius    schedule 13.03.2009
comment
Спасибо за ответ. Я делаю TDD, но, в конце концов, две части не связаны (разве не для этого в конце концов нужен макет)? Для выполнения TDD полного уровня интеграции потребуется проверка типа контейнера внутри EJB. Возможно, но почему-то кажется, что должен быть лучший способ. - person Yishai; 13.03.2009

Если я правильно понимаю вопрос, у вас есть бизнес-объект, который использует модель. Существует тест на взаимодействие между BO и Моделью (Тест A), и есть еще один тест, который проверяет взаимодействие между моделью и базой данных (Тест B). Тест B изменяется, чтобы вернуть объект, но это изменение не влияет на тест A, потому что модель теста A имитируется.

Единственный способ, который я вижу, чтобы сделать тест A непройденным, когда тест B изменяется, — это не имитировать модель в тесте A и объединить два теста в один тест, что не очень хорошо, потому что вы будете тестировать слишком много (и вы с использованием разных фреймворков).

Если вы знаете об этой зависимости, когда пишете тесты, я думаю, что приемлемым решением будет оставить комментарий в каждом тесте с описанием зависимости и того, как если один изменится, вам нужно изменить другой. Вам все равно придется изменить тест B при рефакторинге, текущий тест завершится ошибкой, как только вы внесете изменения.

person Asa Ayers    schedule 13.05.2009

Ваш вопрос довольно сбивает с толку, и количество текста не совсем помогает.

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

Мокирование позволяет вам сконцентрироваться на тестировании определенной части системы. Смоделированная часть всегда будет работать определенным образом, и тест может сосредоточиться на проверке конкретной логики, которой он должен. Таким образом, на вас не будут влиять посторонняя логика, проблемы с задержкой, неожиданные данные и т. д.

У вас, вероятно, будет отдельное количество тестов, проверяющих фиктивную функциональность в другом контексте.

Дело в том, что между фиктивным интерфейсом и его реальной реализацией вообще не должно существовать никакой связи. Это просто не имеет никакого смысла, так как вы издеваетесь над контрактом и даете ему собственную реализацию.

person Rune Sundling    schedule 08.05.2009
comment
Суть вопроса в том, что контракт будет меняться в соответствии с развивающейся структурой, и как обеспечить, чтобы обе стороны признали необходимость изменений. - person Yishai; 08.05.2009

Я думаю, что ваша проблема заключается в нарушении принципа замены Лискова:

Подтипы должны заменять свои базовые типы

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

Технически может показаться, что вы зависите от абстракции, но в описанном вами сценарии вы на самом деле зависите не от абстракции, а от реализации. Вы говорите, что «если этот метод изменит свое поведение, его пользователи сломаются, и мои тесты никогда не узнают». На уровне юнит-тестов вы правы. Но на уровне контракта изменять поведение таким образом неправильно. Потому что, изменяя метод, вы явно нарушаете контракт между вашим методом и его вызывающими.

Почему вы меняете метод? Понятно, что вызывающим этот метод теперь нужно другое поведение. Итак, первое, что вы хотите сделать, это не изменить сам метод, а изменить абстракцию или контракт, от которого зависят ваши клиенты. Они должны сначала измениться и начать работать с новым контрактом: «Хорошо, мои потребности изменились, я больше не хочу, чтобы этот метод возвращал то, что в данном конкретном сценарии, вместо этого разработчики этого интерфейса должны возвращать это». Итак, вы меняете свой интерфейс, вы меняете пользователей интерфейса по мере необходимости, включая обновление их тестов, и последнее, что вы делаете, — это меняете фактическую реализацию, которую вы передаете своим клиентам. Таким образом, вы не столкнетесь с ошибкой, о которой говорите.

So,

class NeedsWork(IWorker b) { DoSth() { b.Work() }; }
...
AppBuilder() { INeedWork GetA() { return new NeedsWork(new Worker()); } }
  1. Измените IWorker, чтобы он отражал новые потребности NeedsWork.
  2. Измените DoSth так, чтобы он работал с новой абстракцией, удовлетворяющей его новым потребностям.
  3. Протестируйте NeedsWork и убедитесь, что он работает с новым поведением.
  4. Измените все реализации (Worker в этом сценарии), которые вы предоставляете для IWorker (что вы сейчас пытаетесь сделать в первую очередь).
  5. Тестируйте Worker, чтобы он соответствовал новым ожиданиям.

Кажется страшным, но в реальной жизни это было бы тривиально для небольших изменений и болезненно для больших изменений, как, собственно, и должно быть.

person Serhat Ozgel    schedule 08.05.2009
comment
Спасибо за Ваш ответ. Однако ответ на самом деле не касается такой структуры, как JMock, где вы на самом деле не создаете конкретные классы, реализующие четко определенный контракт. Идея состоит в том, чтобы тестировать конкретное поведение, а не весь контракт, что делает тесты намного меньше. - person Yishai; 10.05.2009