Как избежать дублирующейся логики с Насмешками

Решение - это просто логический контейнер для проектов, поэтому вы можете создать несколько решений, которые будут ссылаться на одни и те же (существующие) проекты. Это довольно распространено для больших решений, таких как, например, Xamarin.Forms - вы можете иметь одно большое решение со всеми проектами, а затем иметь меньшие решения для разработчиков, которым нужно работать только с подмножеством проектов. [111 ]

Вы можете добавить существующий проект к своему решению, щелкнув правой кнопкой мыши решение в Solution Explorer и выбрав Добавить - Существующий проект .

7
задан Yishai 13 May 2009 в 15:18
поделиться

11 ответов

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

2
ответ дан 6 December 2019 в 19:42
поделиться

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

[РЕДАКТИРОВАНИЕ] На основе комментария: насмешка ничего не должна делать кроме разрешения инстанцировать класса под тестом и позволить собирать дополнительную информацию. Особенно, это никогда не должно не влиять на результат того, что Вы хотите протестировать.

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

Я соглашаюсь, это занимает время для привыкания к. Вот то, что я делаю:

  • У меня есть тесты, которые проверяют, работает ли SQL. Каждый SQL выполняется однажды против статического испытания DB, и я проверяю, что возвращенные данные - то, что я ожидаю.
  • Весь другой тестовый прогон с ложным коннектором DB, который возвращает предопределенные результаты. Мне нравится получать эти результаты путем выполнения кода против базы данных, входа первичных ключей где-нибудь. Я затем пишу инструмент, который берет эти первичные ключи и выводит код Java с насмешкой к System.out. Таким образом, я могу создать новые тестовые сценарии очень быстро, и тестовые сценарии отразят "истину".

    Еще лучше я могу воссоздать старые тесты (когда DB изменяется) путем выполнения старых идентификаторов и моего инструмента снова

2
ответ дан 6 December 2019 в 19:42
поделиться

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

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

0
ответ дан 6 December 2019 в 19:42
поделиться

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

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

Mocking - это инструмент, позволяющий вам сосредоточиться на тестировании определенной части системы. Проверенная часть всегда будет работать определенным образом, и тест может сосредоточиться на проверке конкретной логики, которая должна. Таким образом, вы не будете затронуты несвязанной логикой, проблемами с задержкой, непредвиденными данными и т. Д.

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

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

-1
ответ дан 6 December 2019 в 19:42
поделиться

You're not missing something here. This is a weakness in unit testing with mock objects. It sounds like you are properly breaking your unit tests down into reasonably sized units. This is a good thing; it's far more common to find people testing too much in a "unit" test.

Unfortunately, when you test at this level of granularity, your unit tests don't cover the interaction between collaborating objects. You need to have some integration tests or functional tests to cover this. I don't really know a better answer than that.

Sometimes it's practical to use the real collaborator instead of a mock in your unit test. For example, if you're unit testing a data access object, using the real domain object in the unit test instead of a mock is often easy enough to set up and performs just as well. The reverse is often not true -- data access objects typically need a database connection, file or network connection and are pretty complicated and time consuming to set up; using a real data object when unit testing your domain object will turn a unit test that takes microseconds into one that takes hundreds or thousands of milliseconds.

So to summarize:

  1. Write some integration/functional testing to catch problems with collaborating objects
  2. It's not always necessary to mock out collaborators -- use your best judgement
4
ответ дан 6 December 2019 в 19:42
поделиться

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

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

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

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

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

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

5
ответ дан 6 December 2019 в 19:42
поделиться

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

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

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

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

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

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

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

1
ответ дан 6 December 2019 в 19:42
поделиться

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

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

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

0
ответ дан 6 December 2019 в 19:42
поделиться

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

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

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

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

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

Итак,

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. Test Worker so что это соответствует новым ожиданиям.

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

-2
ответ дан 6 December 2019 в 19:42
поделиться

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

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

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

Это не совсем то же самое. , но это определенно напоминает мне статью Мартина Фаулера Mocks Aren't Stubs . Я рассматриваю маршрут JMock как метод имитации , а маршрут «реальных объектов» как классический способ проведения тестирования.

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

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

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

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

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

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

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

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

1
ответ дан 6 December 2019 в 19:42
поделиться

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

Проблема

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

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

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

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

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

Ваш QA не сработает - хотя ваши тесты не прошли.

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

Решение

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

Это сложно добавить позже. - Так что начните заранее и расширяйте свои тесты.

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

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

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

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


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

1
ответ дан 6 December 2019 в 19:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: