Когда использовать Mockito.verify()?

Я пишу jUnit тестовые примеры для трех целей:

  • Чтобы мой код удовлетворял всем требуемым функциям, под всеми (или большей частью) комбинациями/значениями ввода.
  • Чтобы я мог изменить реализацию и полагаться на тестовые примеры JUnit, чтобы сказать мне, что все мои функциональные возможности все еще удовлетворены.
  • Как документация обо всех случаях использования моего кода, и выступать в качестве спецификации для рефакторинга - должен ли код когда-либо быть переписан. (Рефакторируйте код, и если мои тесты jUnit не пройдут - вы, вероятно, пропустили какой-то прецедент).

Я не понимаю, почему или когда Mockito.verify() следует использовать. Когда я вижу вызываемый verify(), он говорит мне, что мой jUnit осознает реализацию. (Таким образом, изменение моей реализации нарушит мои jUnits, хотя моя функциональность не была затронута).

Я ищу:

  • Какими должны быть руководящие принципы для соответствующего использования Mockito.verify()?

  • Является ли это принципиально правильным для jUnits, чтобы быть в курсе или тесно связан с реализацией тестируемого класса?

Ответ 1

Это фантастический вопрос. +1.

Если договор класса A включает в себя тот факт, что он вызывает метод B объекта типа C, вы должны проверить это, сделав макет типа C и проверив, что был вызван метод B.

Это означает, что контракт класса A имеет достаточно подробно, что он говорит о типе C (который может быть интерфейсом или классом). Итак, да, мы говорим о уровне спецификации, который выходит за рамки только "системных требований" и имеет некоторый способ описания реализации.

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

Update:

Я чувствую, что это относится не только к проверке, но и к обучению. Как только вы закроете метод класса соавторов, ваш unit test стал, в некотором смысле, зависимым от реализации. Это похоже на характер модульных тестов. Поскольку Mockito - это как-то так же, как и проверка, тот факт, что вы используете Mockito, подразумевает, что вы будете сталкиваться с такой зависимостью.

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

Итак, вот что такое модульные тесты. Тест, который не страдает от такой зависимости от того, как используются классы соавторов, действительно является подсистемным тестом или тестом интеграции. Конечно, они часто пишутся вместе с JUnit и часто включают в себя использование насмешек. По моему мнению, "JUnit" - ужасное имя, для продукта, который позволяет нам производить все различные типы тестов.

Ответ 2

Ответ Дэвида, конечно, правильный, но не совсем объясняет, зачем вам это нужно.

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

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

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

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

Ответ 3

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

  • Должен ли я использовать Mockito.verify() в моем тестировании интеграции (или любом другом тестировании с более высоким уровнем)?
  • Должен ли я использовать Mockito.verify() в модульном тестировании черного ящика?
  • Должен ли я использовать Mockito.verify() в модульном тестировании white-box?

поэтому, если мы проигнорируем тестирование более высокого уровня, вопрос может быть перефразирован. "Использование white-box unit-testing с помощью Mockito.verify() создает большую пару между unit test и my может ли я выполнить некоторые " серое окно " модульное тестирование и какие правила большого пальца я должен использовать для этого".

Теперь пропустите все это шаг за шагом.

* - Должен ли я использовать Mockito.verify() для тестирования интеграции (или любого другого тестирования, отличного от единицы измерения)? * Я думаю, что ответа явно нет, более того, вы не должны использовать для этого макеты. Ваш тест должен быть как можно ближе к реальному приложению. Вы тестируете полный прецедент, а не отдельную часть приложения.

* черный ящик vs белый ящик unit-testing * Если вы используете подход черный ящик, что вы действительно делаете, вы вводите (все классы эквивалентности) вход, состояние и тесты, которые вы получите ожидаемый результат. В этом подходе использование оправданий в целом оправдано (вы просто имитируете, что они поступают правильно, вы не хотите их тестировать), но вызов Mockito.verify() является излишним.

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

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

Это действительно сложно. У меня нет хорошего примера, но я могу привести примеры. В случае, упомянутом выше с equals() vs equalsIgnoreCase(), вы не должны называть Mockito.verify(), просто подтверждайте вывод. Если вы не смогли этого сделать, сложите свой код на меньшую единицу, пока вы не сможете это сделать. С другой стороны, предположим, что у вас есть @Service, и вы пишете @Web-сервис, который по существу является оберткой на вашем @Service - он делегирует все вызовы @Service (и делает дополнительную обработку ошибок). В этом случае необходимо обратиться к Mockito.verify(), вам не следует дублировать все ваши проверки, которые вы сделали для @Serive, достаточно проверить, что вы вызываете @Service с правильным списком параметров.

Ответ 4

Я должен сказать, что вы абсолютно правы с точки зрения классического подхода:

  • Если вы сначала создаете (или изменяете) бизнес-логику своего приложения, а затем покрываете его (принятие) тестов (подход Test-Last), тогда будет очень болезненно и опасно давать тестам знать что-либо о том, как работает ваше программное обеспечение, кроме проверки входов и выходов.
  • Если вы практикуете Test-Driven, то ваши тесты будут сначала для написания, изменения и отображения вариантов использования ваших программных функций. Реализация зависит от тестов. Это иногда означает, что вы хотите, чтобы ваше программное обеспечение было реализовано определенным образом, например. полагаться на какой-то другой компонентный метод или даже называть его определенное количество раз. Именно здесь Mockito.verify() пригодится!

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

Ответ 5

Как говорили некоторые люди

  • Иногда у вас нет прямого вывода, на котором вы можете утверждать
  • Иногда вам просто нужно подтвердить, что ваш протестированный метод отправляет правильные косвенные результаты своим сотрудникам (которые вы издеваетесь).

Относительно вашей озабоченности по поводу нарушения ваших тестов при рефакторинге, это несколько ожидается при использовании mocks/stubs/spies. Я имею в виду, что по определению, а не в отношении конкретной реализации, такой как Mockito. Но вы могли бы подумать таким образом - если вам нужно сделать рефакторинг, который создаст серьезные изменения в способе работы вашего метода, рекомендуется сделать это на основе TDD-подхода, то есть вы можете сначала изменить свой тест , чтобы определить новое поведение (которое не пройдет тест) и затем выполнить изменения и снова пройти тест.