Я пишу тестовые примеры jUnit для 3 целей:
- Чтобы гарантировать, что мой код удовлетворяет всем требуемым функциям, под всеми (или большинством) входными комбинациями / значениями.
- Чтобы убедиться, что я могу изменить реализацию, и положиться на контрольные примеры JUnit, чтобы сказать мне, что все мои функциональные возможности все еще удовлетворены.
- В качестве документации по всем сценариям использования мой код обрабатывается и выступает в качестве спецификации для рефакторинга - если когда-либо потребуется переписать код. (Измените код, и если мои тесты jUnit не пройдут - вы, вероятно, пропустили какой-то вариант использования).
Я не понимаю, почему или когда Mockito.verify()
следует использовать. Когда я вижу verify()
, что мне звонят, он говорит мне, что мой jUnit узнает о реализации. (Таким образом, изменение моей реализации сломало бы мои jUnits, хотя моя функциональность не была затронута).
Я ищу:
Какими должны быть рекомендации по правильному использованию
Mockito.verify()
?Правильно ли для jUnits быть в курсе или тесно связано с реализацией тестируемого класса?
java
unit-testing
junit
mockito
Рассел
источник
источник
Ответы:
Если контракт класса A включает в себя тот факт, что он вызывает метод B объекта типа C, то вы должны проверить это, сделав макет типа C и проверив, что метод B был вызван.
Это подразумевает, что контракт класса A имеет достаточную детализацию, чтобы говорить о типе C (который может быть интерфейсом или классом). Так что да, мы говорим об уровне спецификации, который выходит за рамки просто «системных требований» и идет каким-то образом к описанию реализации.
Это нормально для юнит-тестов. Когда вы проводите юнит-тестирование, вы хотите убедиться, что каждый юнит выполняет «правильные действия», и это обычно включает его взаимодействие с другими юнитами. «Единицы» здесь могут означать классы или большие подмножества вашего приложения.
Обновить:
Я чувствую, что это относится не только к проверке, но и к заглушке. Как только вы заглушаете метод класса соавтора, ваш модульный тест в некотором смысле становится зависимым от реализации. Это своего рода модульные тесты. Поскольку Mockito так же важен как для проверки, так и для проверки, тот факт, что вы вообще используете Mockito, подразумевает, что вы столкнетесь с такой зависимостью.
По моему опыту, если я изменяю реализацию класса, мне часто приходится менять реализацию его модульных тестов, чтобы соответствовать. Как правило, хотя, я не должен изменить инвентаризацию того , что испытания блока есть являются для класса; если, конечно, причиной изменения было наличие условия, которое я не смог проверить ранее.
Вот что такое юнит-тесты. Тест, который не страдает от такой зависимости от того, как используются классы коллабораторов, на самом деле является тестом подсистемы или интеграционным тестом. Конечно, они также часто пишутся с помощью JUnit и часто включают использование насмешек. На мой взгляд, «JUnit» - ужасное название для продукта, который позволяет нам производить все виды тестов.
источник
equals()
илиequalsIgnoreCase()
никогда не будет что-то, что было указано в требованиях класса, так что никогда не было бы модульного теста как такового. Однако «закрытие соединения с БД по окончании» (что бы это ни значило с точки зрения реализации) вполне может быть требованием класса, даже если это не «бизнес-требование». Для меня это сводится к отношениям между контрактом ...Ответ Дэвида, конечно, правильный, но не совсем объясняет, почему вы этого хотите.
По сути, при модульном тестировании вы тестируете функциональную единицу изолированно. Вы проверяете, дает ли вход ожидаемый результат. Иногда вам также нужно проверить побочные эффекты. Короче говоря, проверка позволяет вам сделать это.
Например, у вас есть немного бизнес-логики, которая должна хранить вещи с использованием DAO. Вы можете сделать это с помощью интеграционного теста, который создает экземпляр DAO, подключает его к бизнес-логике, а затем копается в базе данных, чтобы посмотреть, сохранены ли ожидаемые данные. Это больше не юнит тест.
Или вы могли бы посмеяться над DAO и убедиться, что он вызывается так, как вы ожидаете. С помощью mockito вы можете убедиться, что что-то вызывается, как часто оно вызывается, и даже использовать сопоставления параметров, чтобы убедиться, что оно вызывается определенным образом.
Обратная сторона модульного тестирования, подобного этому, заключается в том, что вы привязываете тесты к реализации, что делает рефакторинг немного сложнее. С другой стороны, хороший дизайнерский запах - это количество кода, необходимого для его правильного использования. Если ваши тесты должны быть очень длинными, возможно, что-то не так с дизайном. Так что код с большим количеством побочных эффектов / сложных взаимодействий, которые необходимо протестировать, вероятно, не очень хорошая вещь.
источник
Это отличный вопрос! Я думаю, что основная причина этого заключается в следующем, мы используем JUnit не только для модульного тестирования. Таким образом, вопрос должен быть разделен:
так что, если мы будем игнорировать тестирование выше, чем модуль, вопрос можно перефразировать: « Использование модульного тестирования белого ящика с Mockito.verify () создает отличную пару между модульным тестом и моей возможной реализацией, могу ли я создать некоторый « серый ящик » « юнит-тестирование и какие практические правила я должен использовать для этого ».
Теперь давайте пройдемся по всему этому шаг за шагом.
* - Должен ли я использовать Mockito.verify () в моей интеграции (или любом другом тестировании, превышающем единицу) тестировании? * Я думаю, что ответ однозначно нет, более того, вы не должны использовать макеты для этого. Ваш тест должен быть максимально приближен к реальному применению. Вы тестируете полный вариант использования, а не изолированную часть приложения.
* черный ящик против модульного тестирования белого ящика * Если вы используете подход черного ящика, что вы действительно делаете, вы предоставляете (все классы эквивалентности) входные данные, состояние и тесты, которые вы получите ожидаемый результат. При таком подходе использование mocks в целом оправдывает (вы просто имитируете, что они делают правильные вещи; вы не хотите их проверять), но вызов Mockito.verify () излишен.
Если вы используете подход « белого ящика», что вы действительно делаете, вы тестируете поведение своего устройства. В этом подходе необходим вызов Mockito.verify (), вы должны убедиться, что ваш юнит ведет себя так, как вы ожидаете.
правила большого пальца для тестирования « серого ящика» Проблема с тестированием белого ящика заключается в том, что оно создает высокую связь. Одним из возможных решений является тестирование «серого ящика», а не «белого ящика». Это своего рода комбинация черно-белого тестирования. Вы действительно тестируете поведение вашего модуля, как в тесте белого ящика, но в целом вы делаете его независимым от реализации, когда это возможно . Когда это возможно, вы просто сделаете проверку, как в случае с черным ящиком, просто подтвердите, что выход - это то, что вы ожидаете. Итак, суть вашего вопроса в том, когда это возможно.
Это действительно сложно. У меня нет хорошего примера, но я могу привести примеры. В случае, который был упомянут выше с помощью equals () vs equalsIgnoreCase (), вы не должны вызывать Mockito.verify (), просто подтвердить вывод. Если вы не смогли этого сделать, разбейте ваш код на меньшие единицы, пока не сможете это сделать. С другой стороны, предположим, что у вас есть некоторый @Service и вы пишете @ Web-Service, который по сути является оберткой для вашего @Service - он делегирует все вызовы @Service (и делает некоторую дополнительную обработку ошибок). В этом случае необходим вызов Mockito.verify (), вам не следует дублировать все свои проверки, которые вы делали для @Serive, достаточно убедиться, что вы вызываете @Service с правильным списком параметров.
источник
Должен сказать, что вы абсолютно правы с точки зрения классического подхода:
Важно помнить, что нет универсальных инструментов. Тип программного обеспечения, его размер, цели компании и рыночная ситуация, командные навыки и многое другое влияют на решение о том, какой подход использовать в вашем конкретном случае.
источник
Как говорили некоторые люди
Что касается вашего беспокойства по поводу нарушения ваших тестов при рефакторинге, то это вполне ожидаемо при использовании mocks / stubs / spies. Я имею в виду, что по определению, а не в отношении конкретной реализации, такой как Mockito. Но вы могли бы подумать так: если вам нужно провести рефакторинг, который приведет к серьезным изменениям в том, как работает ваш метод, это хорошая идея сделать это с подходом TDD, то есть вы можете сначала изменить свой тест, чтобы определить новое поведение (которое не пройдёт тест), а затем внесите изменения и получите тест снова пройденным.
источник
В большинстве случаев, когда людям не нравится использовать Mockito.verify, это потому, что он используется для проверки всего, что делает тестируемый модуль, и это означает, что вам нужно будет адаптировать свой тест, если что-то изменится в нем. Но я не думаю, что это проблема. Если вы хотите иметь возможность изменять то, что делает метод без необходимости менять его тест, это в основном означает, что вы хотите писать тесты, которые не проверяют все, что делает ваш метод, потому что вы не хотите, чтобы он тестировал ваши изменения , И это неправильный способ мышления.
Что действительно является проблемой, так это то, что вы можете изменить то, что делает ваш метод, и модульный тест, который должен полностью охватить функциональность, не провалится. Это означало бы, что независимо от цели вашего изменения, результат вашего изменения не охватывается тестом.
Из-за этого я предпочитаю издеваться как можно больше: также издеваться над объектами данных. При этом вы можете не только использовать verify, чтобы проверить, что вызываются правильные методы других классов, но также и то, что передаваемые данные собираются с помощью правильных методов этих объектов данных. И чтобы сделать это, вы должны проверить порядок, в котором происходят вызовы. Пример: если вы изменяете объект сущности db, а затем сохраняете его с помощью репозитория, недостаточно проверить, что установщики объекта вызываются с правильными данными и что вызывается метод сохранения репозитория. Если они вызываются в неправильном порядке, ваш метод все равно не выполняет то, что должен делать. Итак, я не использую Mockito.verify, но создаю объект inOrder со всеми имитаторами и вместо этого использую inOrder.verify. И если вы хотите завершить его, вам также следует позвонить в Mockito. verifyNoMoreInteractions в конце и передать все это макеты. В противном случае кто-то может добавить новую функциональность / поведение без его тестирования, что может означать, что через некоторое время ваша статистика покрытия может быть 100%, и все же вы накапливаете код, который не утвержден или не проверен.
источник