Всем привет!
Не JUnit-ом единым...
Если говорить о фреймворках для unit тестирования все наверняка вспомнят JUnit и Mockito. Но есть ещё много чего полезного в этой области. Сегодня расскажу про библиотеки для улучшения читаемости проверок - assert. Про важность читаемости кода, в т.ч тестового я уже писал.
Пример для затравки из AssertJ
assertThat(testList).hasSize(2) .containsExactly("index3", "index4") .isUnmodifiable();
Больше примеров см по ссылкам в конце поста.
Библиотек три.
1) Hamcrest. Старичок, родоначальник жанра, уже не развивается, не рекомендуется к использованию
2) AssertJ - в отличие от hamcrest построен на принципе method chaining, что позволяет использовать автопополнение IDE и выглядит более читаемо. Выводит более понятное сообщение об ошибке, что тоже важно. Есть фича Soft Assertion, позволяющая лениво описать n проверок и выполнить их за раз.
3) Truth - очень похож по принципу работы - method chaining - на AssertJ, при этом менее известен. В качестве преимущества его разработчики указывают более компактное API и более понятное логирование ошибок.
Как AssertJ, так и Truth позволяют создавать свои проверки.
За деталями предлагаю пойти сюда:
https://dzone.com/articles/hamcrest-vs-assertj-assertion-frameworks-which-one
https://habr.com/ru/post/675778/
https://truth.dev/comparison.html
#unittests #rare_test_libs
Не JUnit-ом единым...
Если говорить о фреймворках для unit тестирования все наверняка вспомнят JUnit и Mockito. Но есть ещё много чего полезного в этой области. Сегодня расскажу про библиотеки для улучшения читаемости проверок - assert. Про важность читаемости кода, в т.ч тестового я уже писал.
Пример для затравки из AssertJ
assertThat(testList).hasSize(2) .containsExactly("index3", "index4") .isUnmodifiable();
Больше примеров см по ссылкам в конце поста.
Библиотек три.
1) Hamcrest. Старичок, родоначальник жанра, уже не развивается, не рекомендуется к использованию
2) AssertJ - в отличие от hamcrest построен на принципе method chaining, что позволяет использовать автопополнение IDE и выглядит более читаемо. Выводит более понятное сообщение об ошибке, что тоже важно. Есть фича Soft Assertion, позволяющая лениво описать n проверок и выполнить их за раз.
3) Truth - очень похож по принципу работы - method chaining - на AssertJ, при этом менее известен. В качестве преимущества его разработчики указывают более компактное API и более понятное логирование ошибок.
Как AssertJ, так и Truth позволяют создавать свои проверки.
За деталями предлагаю пойти сюда:
https://dzone.com/articles/hamcrest-vs-assertj-assertion-frameworks-which-one
https://habr.com/ru/post/675778/
https://truth.dev/comparison.html
#unittests #rare_test_libs
DZone
Hamcrest vs. AssertJ
Let's compare two popular open Java assertion frameworks, Hamcrest and AssertJ, for testing RESTful APIs. Check out their features, syntax, and other perks.
Всем привет!
Давно хотел написать про паттерны/шаблоны программирования. Основной вопрос, возникающий при разговоре про паттерны - какая от них польза? Ведь главное - умеет человек кодить или нет.
С одной стороны паттерны - это лишь часть арсенала программиста. Можно заучить все паттерны, но не научиться кодить.
И тут возникает второй вопрос - о каких паттернах мы говорим?
1) самые известные - паттерны проектирования из книги «банды четырёх» https://refactoring.guru/ru/design-patterns/catalog
Это синглтон, фабричный метод, билдер и все все все
2) паттерны Enterprise архитектуры от Фаулера https://martinfowler.com/eaaCatalog/
3) паттерны рефакторинга https://refactoring.com/catalog/ Про них также говорится в книге Идеальная работа Мартина
4) паттерны модульных тестов http://xunitpatterns.com/ и снова в книге Идеальная работа
5) паттерны интеграции корпоративных приложений https://www.enterpriseintegrationpatterns.com/patterns/messaging/toc.html многие из которых можно встретить в стандарте JMS
6) паттерны микросервисных приложений https://microservices.io/patterns/index.html
7) даже у Kubernates есть паттерны https://www.redhat.com/cms/managed-files/cm-oreilly-kubernetes-patterns-ebook-f19824-201910-en.pdf
8) не говоря уже про антипаттерны https://javarush.ru/groups/posts/2622-chto-takoe-antipatternih-razbiraem-primerih-chastjh-1
9) 10) ...
Из этого списка можно сделать вывод, что паттерны могут быть везде. А из этого второй вывод: паттерны - это удобный способ описания какой-то области разработки. Собственно это и есть их ценность. Шаблоны помогают изучить новую технологию, читать статьи, книги и главное читать код и тесты. Ну и проектировать систему, обсуждать ее архитектуру с коллегами. По сути паттерны - это язык проектирования. А идеальный способ их использования - когда они уже реализованы в неком фреймворке: Singleton и MVC в Spring, Builder в Lombok, Sidecar в k8s, или в языке как Singleton и Decorator в Kotlin.
#patterns #refactoring #unittests
Давно хотел написать про паттерны/шаблоны программирования. Основной вопрос, возникающий при разговоре про паттерны - какая от них польза? Ведь главное - умеет человек кодить или нет.
С одной стороны паттерны - это лишь часть арсенала программиста. Можно заучить все паттерны, но не научиться кодить.
И тут возникает второй вопрос - о каких паттернах мы говорим?
1) самые известные - паттерны проектирования из книги «банды четырёх» https://refactoring.guru/ru/design-patterns/catalog
Это синглтон, фабричный метод, билдер и все все все
2) паттерны Enterprise архитектуры от Фаулера https://martinfowler.com/eaaCatalog/
3) паттерны рефакторинга https://refactoring.com/catalog/ Про них также говорится в книге Идеальная работа Мартина
4) паттерны модульных тестов http://xunitpatterns.com/ и снова в книге Идеальная работа
5) паттерны интеграции корпоративных приложений https://www.enterpriseintegrationpatterns.com/patterns/messaging/toc.html многие из которых можно встретить в стандарте JMS
6) паттерны микросервисных приложений https://microservices.io/patterns/index.html
7) даже у Kubernates есть паттерны https://www.redhat.com/cms/managed-files/cm-oreilly-kubernetes-patterns-ebook-f19824-201910-en.pdf
8) не говоря уже про антипаттерны https://javarush.ru/groups/posts/2622-chto-takoe-antipatternih-razbiraem-primerih-chastjh-1
9) 10) ...
Из этого списка можно сделать вывод, что паттерны могут быть везде. А из этого второй вывод: паттерны - это удобный способ описания какой-то области разработки. Собственно это и есть их ценность. Шаблоны помогают изучить новую технологию, читать статьи, книги и главное читать код и тесты. Ну и проектировать систему, обсуждать ее архитектуру с коллегами. По сути паттерны - это язык проектирования. А идеальный способ их использования - когда они уже реализованы в неком фреймворке: Singleton и MVC в Spring, Builder в Lombok, Sidecar в k8s, или в языке как Singleton и Decorator в Kotlin.
#patterns #refactoring #unittests
refactoring.guru
Каталог паттернов проектирования
Список паттернов проектирования, сгруппированый по предназанчению, сложности и популярности паттернов. В каталог включены объектно ориентированные паттерны, а также некоторые архитектурные паттерны.
Всем привет!
По следам отличной книги Владимира Хорикова Принципы юнит-тестирования, см https://t.me/javaKotlinDevOps/50, решил погрузится в тему и прочитать еще и классику жанра - Шаблоны тестирования xUnit. Рефакторинг кода тестов https://www.litres.ru/dzherard-mesarosh/shablony-testirovaniya-xunit-refaktoring-koda-t-48637685/
К слову, на английском книга выложена в виде сайта со списком паттернов тестирования http://xunitpatterns.com/
По сути книга и является большим справочников паттернов. Автор - человек дотошный: любую типовую практику, используемую для написания тестов он вынес в шаблон с подробным описанием истории возникнования, вариантов релизации и примеров, мотивирующих на использование шаблонов.
Вот примеры шаблонов из книги, на которые хотел бы обратить внимание:
1) 5 видов моков http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html - от Dummy object, который ничего не возвращает, не делает и не проверяет, а нужен лишь для передачи как параметр, до Fake Object - облегченной реализации боевой логики, без обращений во внешние системы и БД. Видел ссылки на эти 5 типов в других статьях и книгах.
2) Test Utility Method - одна из базовых практик при рефаторинге тестов, вынесение повторяющего кода в отдельные методы. Это может быть код настройки системы, код проверки результата с assert-ми и код очистики ресурсов. Очень полезная штука, с какого-то момента после рефакторинга вынесенных методов для их переиспользования мы получаем некий тестовый язык, по сути свой DSL, и это без всяких Cucumber-ов и прочих BDD. http://xunitpatterns.com/Test%20Utility%20Method.html
Остальное не влезло в пост телеги, см. на дзене https://dzen.ru/media/id/62fe543369f7d3314503a87d/recenziia-na-shablony-testirovaniia-xunit-6367bbba7d9dc52b40aa3ff3
#unittests #books
По следам отличной книги Владимира Хорикова Принципы юнит-тестирования, см https://t.me/javaKotlinDevOps/50, решил погрузится в тему и прочитать еще и классику жанра - Шаблоны тестирования xUnit. Рефакторинг кода тестов https://www.litres.ru/dzherard-mesarosh/shablony-testirovaniya-xunit-refaktoring-koda-t-48637685/
К слову, на английском книга выложена в виде сайта со списком паттернов тестирования http://xunitpatterns.com/
По сути книга и является большим справочников паттернов. Автор - человек дотошный: любую типовую практику, используемую для написания тестов он вынес в шаблон с подробным описанием истории возникнования, вариантов релизации и примеров, мотивирующих на использование шаблонов.
Вот примеры шаблонов из книги, на которые хотел бы обратить внимание:
1) 5 видов моков http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html - от Dummy object, который ничего не возвращает, не делает и не проверяет, а нужен лишь для передачи как параметр, до Fake Object - облегченной реализации боевой логики, без обращений во внешние системы и БД. Видел ссылки на эти 5 типов в других статьях и книгах.
2) Test Utility Method - одна из базовых практик при рефаторинге тестов, вынесение повторяющего кода в отдельные методы. Это может быть код настройки системы, код проверки результата с assert-ми и код очистики ресурсов. Очень полезная штука, с какого-то момента после рефакторинга вынесенных методов для их переиспользования мы получаем некий тестовый язык, по сути свой DSL, и это без всяких Cucumber-ов и прочих BDD. http://xunitpatterns.com/Test%20Utility%20Method.html
Остальное не влезло в пост телеги, см. на дзене https://dzen.ru/media/id/62fe543369f7d3314503a87d/recenziia-na-shablony-testirovaniia-xunit-6367bbba7d9dc52b40aa3ff3
#unittests #books
Telegram
(java || kotlin) && devOps
Всем привет! Ещё одно ревью на книгу, которую упоминал: Владимир Хориков Принципы юнит-тестирования. https://habr.com/ru/company/piter/blog/528872/ Можно сказать уникальная книга про то, как писать юнит тесты. Я знаю таких всего две - эту и Шаблоны тестирования…
Всем привет!
Надеюсь все уже перешли на JUnit 5?
У JUnit 5 много крутых фич по сравнению с 4-й версией, например:
1) более удобная работа с параметризованными тестами через @ParametrizedTest, который вешается на метод - можно миксовать параметризированные и непараметризированные тесты в одном тестовом классе
2) возможность параметризировать не только входные параметры, но и все остальное в тесте с помощью динамически генерируемых тестов DynamicTests https://www.baeldung.com/junit5-dynamic-tests
3) есть возможность проверки исключений и таймаута через методы - assertThrows и assertTimeout. Это более правильно - assert идет после Act (я про Arrange-Act-Assert) и позволяет делать несколько проверок в одном тестовом методе
4) условное выполнение тестов в зависимости от среды: @EnabledOnOs, @EnabledOnJre, @EnabledIfSystemProperty, @EnabledIfEnvironmentVariable, также можно создавать свои условия
5) @RepeatedTest для повтора теста N раз
6) @DisplayName - полезно для задания говорящего имени теста для параметризированных тестов, к примеру
7) @Nested для складывания тестовых классов в "матрешку". Полезно, если у часть тестов нужно выполнить отнаследовавшись от базового класса
8) assertAll - объединение нескольких assert-ов в тесте. Полезно, как способ отключить fail fast режим проверок в JUnit - прогнать все assert-ы в тесте несмотря на падение одного из них.
9) ну и наконец механизм extensions. В 4-й версии был механизм Runner-ов, но его ограничение состояло в том, что на класс можно было повесить один Runner. А extensions может быть сколько угодно. Вот пример реализации BDD с помощью extension https://www.infoq.com/articles/deep-dive-junit5-extensions/ Здесь же есть пример 3-го способа параметризации теста - через ParameterResolver.
10) кроме всего прочего механизм extension добавляет store - контекст для обмена данными между тестами. Важно: для модульных тестов это антипаттерн, но нормально для интеграционных https://junit.org/junit5/docs/5.0.0/api/org/junit/jupiter/api/extension/ExtensionContext.Store.html
11) указание очередности тестов через @Order - полезно для тестирования отдельных шагов в интеграционном тесте. По умолчанию, кстати, тесты запускаются в алфавитном порядке https://www.baeldung.com/junit-5-test-order
В общем JUnit 5 догнал и перегнал TestNG, рекомендую к использованию если еще не пробовали.
#junit #unittests
Надеюсь все уже перешли на JUnit 5?
У JUnit 5 много крутых фич по сравнению с 4-й версией, например:
1) более удобная работа с параметризованными тестами через @ParametrizedTest, который вешается на метод - можно миксовать параметризированные и непараметризированные тесты в одном тестовом классе
2) возможность параметризировать не только входные параметры, но и все остальное в тесте с помощью динамически генерируемых тестов DynamicTests https://www.baeldung.com/junit5-dynamic-tests
3) есть возможность проверки исключений и таймаута через методы - assertThrows и assertTimeout. Это более правильно - assert идет после Act (я про Arrange-Act-Assert) и позволяет делать несколько проверок в одном тестовом методе
4) условное выполнение тестов в зависимости от среды: @EnabledOnOs, @EnabledOnJre, @EnabledIfSystemProperty, @EnabledIfEnvironmentVariable, также можно создавать свои условия
5) @RepeatedTest для повтора теста N раз
6) @DisplayName - полезно для задания говорящего имени теста для параметризированных тестов, к примеру
7) @Nested для складывания тестовых классов в "матрешку". Полезно, если у часть тестов нужно выполнить отнаследовавшись от базового класса
8) assertAll - объединение нескольких assert-ов в тесте. Полезно, как способ отключить fail fast режим проверок в JUnit - прогнать все assert-ы в тесте несмотря на падение одного из них.
9) ну и наконец механизм extensions. В 4-й версии был механизм Runner-ов, но его ограничение состояло в том, что на класс можно было повесить один Runner. А extensions может быть сколько угодно. Вот пример реализации BDD с помощью extension https://www.infoq.com/articles/deep-dive-junit5-extensions/ Здесь же есть пример 3-го способа параметризации теста - через ParameterResolver.
10) кроме всего прочего механизм extension добавляет store - контекст для обмена данными между тестами. Важно: для модульных тестов это антипаттерн, но нормально для интеграционных https://junit.org/junit5/docs/5.0.0/api/org/junit/jupiter/api/extension/ExtensionContext.Store.html
11) указание очередности тестов через @Order - полезно для тестирования отдельных шагов в интеграционном тесте. По умолчанию, кстати, тесты запускаются в алфавитном порядке https://www.baeldung.com/junit-5-test-order
В общем JUnit 5 догнал и перегнал TestNG, рекомендую к использованию если еще не пробовали.
#junit #unittests
Baeldung
Guide to Dynamic Tests in Junit 5 | Baeldung
Learn about dynamic tests introduced in JUnit 5 - a new programming model that supports full test lifecycle.
Всем привет!
Пару мыслей о BDD фреймворках, самым известным представителем которых является Cucumber.
Пример теста на Cucumber, чтобы было понятно о чем я:
Scenario: Eric wants to withdraw money from his bank account at an ATM
Given Eric has a valid Credit or Debit card
And his account balance is $100
When he inserts his card
And withdraws $45
Then the ATM returns $45
And his account balance is $55
Т.е по сути мы привязываем к методам в коде фразы естественного языка и т.об. можем написать тест как осмысленное предложение.
Мне не понятно, где это может быть полезно.
Основная трудность в BDD - требуется большая подготовительная работа по созданию "словаря", из которого будут конструироваться тесты. Причем если приложение сложное и развивается - работа становится постоянной. Вопрос - а нужно ли делать эту работу?
Рассмотрим возможные случаи использования.
1) разработчики для написания "системных" тестов, проверяющих сервис целиком? Не нужно, разработчику проще написать на обычном JUnit 5, максимум добавить туда Rest Assured как удобным способ в fluent стиле написать тест c четко выделенными стадиями Arrage-Act-Assert "в одну строчку". Оффтоп: почему я слово "в одну строчку" взял в кавычки думаю понятно, а вот насчет "системных" - с терминологией в классификации тестов все плохо, причем когда я начал эту тему изучать лет 6 назад - все было также))) Решение я вижу - определить терминологию в своей команде и ее придерживаться.
2) автотестеры, интеграционное тестирование, тестирование на заглушках, регресс? Как по мне если тестировщик решил перейти в касту автотестеров, то разбираться в Java или Kotlin он должен. Ну или в любом другом языке, где есть хороший тестовый фреймворк. И в DevOps немножко. Очень сложно сделать такую платформу для автотестов, где этих знаний не понадобится.
3) ручные тестировщики? Если тестировщик не хочет в автоматизацию, то я не верю, что BDD может чем-то помочь. Если не прав - хотелось бы узнать про такие кейсы. Снова оффтоп: тестировщик, который не хочет в автоматизацию, не равно плохой тестировщик. Есть роль тест-менеджера, определяющего полноту набора тест-кейсов, состав регресса, пишущего сценарии, и т.об. гарантирующий соответствие кода требованиям. Есть исследовательское тестирование, где автоматизация играет вспомогательную роль.
4) нагрузочное тестирование? Точно не про то: использовать или нет BDD - далеко не та проблема, которая важна для НТ. Профит не понятен
5) системные аналитики? Те, которых я видел "в живой природе", тесты не пишут. У них и так работы хватает: API, схема БД, сиквенс диаграммы, карта экранов, согласования, впитывание и трансформация мыслей продактов...
6) приемочные испытания. Тут развилка. Если мы говорим о приемке внутри одной организации - по моему опыту заинтересованные лица валидируют аналитику заранее, и приемка сводится к демострации и ответах на каверзные вопросы. Возможен прогон тех же автотестов, что и на стадии интеграционного тестирования. И наконец единственный кейс, где BDD может рулить - приемка на стороне организации-заказчика. Для них поставляемое ПО - черный ящик. И то, если процесс не по Agile, и заказчик в процессах команды разработки не участвует.
И еще небольшой гвоздик напоследок - сделать словарь для BDD легко, когда в нем 5-10-20 фраз. Т.е для небольшого приложения. А когда их станет не хватать? Тогда или нужная тяжелая работа по вычистке, универсализации, или словарь станет примитивным - оправь http запрос, прочитай заголовок ответа, а в этом случае BDD становится бесполезным.
#unittests #bdd #testing
Пару мыслей о BDD фреймворках, самым известным представителем которых является Cucumber.
Пример теста на Cucumber, чтобы было понятно о чем я:
Scenario: Eric wants to withdraw money from his bank account at an ATM
Given Eric has a valid Credit or Debit card
And his account balance is $100
When he inserts his card
And withdraws $45
Then the ATM returns $45
And his account balance is $55
Т.е по сути мы привязываем к методам в коде фразы естественного языка и т.об. можем написать тест как осмысленное предложение.
Мне не понятно, где это может быть полезно.
Основная трудность в BDD - требуется большая подготовительная работа по созданию "словаря", из которого будут конструироваться тесты. Причем если приложение сложное и развивается - работа становится постоянной. Вопрос - а нужно ли делать эту работу?
Рассмотрим возможные случаи использования.
1) разработчики для написания "системных" тестов, проверяющих сервис целиком? Не нужно, разработчику проще написать на обычном JUnit 5, максимум добавить туда Rest Assured как удобным способ в fluent стиле написать тест c четко выделенными стадиями Arrage-Act-Assert "в одну строчку". Оффтоп: почему я слово "в одну строчку" взял в кавычки думаю понятно, а вот насчет "системных" - с терминологией в классификации тестов все плохо, причем когда я начал эту тему изучать лет 6 назад - все было также))) Решение я вижу - определить терминологию в своей команде и ее придерживаться.
2) автотестеры, интеграционное тестирование, тестирование на заглушках, регресс? Как по мне если тестировщик решил перейти в касту автотестеров, то разбираться в Java или Kotlin он должен. Ну или в любом другом языке, где есть хороший тестовый фреймворк. И в DevOps немножко. Очень сложно сделать такую платформу для автотестов, где этих знаний не понадобится.
3) ручные тестировщики? Если тестировщик не хочет в автоматизацию, то я не верю, что BDD может чем-то помочь. Если не прав - хотелось бы узнать про такие кейсы. Снова оффтоп: тестировщик, который не хочет в автоматизацию, не равно плохой тестировщик. Есть роль тест-менеджера, определяющего полноту набора тест-кейсов, состав регресса, пишущего сценарии, и т.об. гарантирующий соответствие кода требованиям. Есть исследовательское тестирование, где автоматизация играет вспомогательную роль.
4) нагрузочное тестирование? Точно не про то: использовать или нет BDD - далеко не та проблема, которая важна для НТ. Профит не понятен
5) системные аналитики? Те, которых я видел "в живой природе", тесты не пишут. У них и так работы хватает: API, схема БД, сиквенс диаграммы, карта экранов, согласования, впитывание и трансформация мыслей продактов...
6) приемочные испытания. Тут развилка. Если мы говорим о приемке внутри одной организации - по моему опыту заинтересованные лица валидируют аналитику заранее, и приемка сводится к демострации и ответах на каверзные вопросы. Возможен прогон тех же автотестов, что и на стадии интеграционного тестирования. И наконец единственный кейс, где BDD может рулить - приемка на стороне организации-заказчика. Для них поставляемое ПО - черный ящик. И то, если процесс не по Agile, и заказчик в процессах команды разработки не участвует.
И еще небольшой гвоздик напоследок - сделать словарь для BDD легко, когда в нем 5-10-20 фраз. Т.е для небольшого приложения. А когда их станет не хватать? Тогда или нужная тяжелая работа по вычистке, универсализации, или словарь станет примитивным - оправь http запрос, прочитай заголовок ответа, а в этом случае BDD становится бесполезным.
#unittests #bdd #testing
Всем привет!
Сегодня хочу рассказать про полезную библиотеку для Unit-тестирования - Instancio.
Ее цель - заполнение произвольными данными тестовых объектов. По умолчанию - произвольными и not null.
Но можно настройть селекторы для выбора определенных полей по маске (регулярке) и заполнении их данными по определенному алгоритму, определенными константами или null. Умеет заполнять коллекции и вложенные объекты.
Плюса я вижу два:
1) один очевидный - убрать boiler-plate код из тестового кода
2) второй не такой очевидный - при создании тестовых объектов руками часто во всех тестах используются одни и те же константы. Очевидные, типовые. Есть вероятность не попасть с выбранной тестовой константой в какие-то ветки тестовой логики, и т.об. не протестировать часть логики. Instancio же генерирует произвольные данные и может при очередном запуске помочь поймать редкую ошибку до того, как она проявится на ПРОМе.
Вот неплохая статья с примерами использования https://www.baeldung.com/java-test-data-instancio
#unittests #rare_test_libs
Сегодня хочу рассказать про полезную библиотеку для Unit-тестирования - Instancio.
Ее цель - заполнение произвольными данными тестовых объектов. По умолчанию - произвольными и not null.
Но можно настройть селекторы для выбора определенных полей по маске (регулярке) и заполнении их данными по определенному алгоритму, определенными константами или null. Умеет заполнять коллекции и вложенные объекты.
Плюса я вижу два:
1) один очевидный - убрать boiler-plate код из тестового кода
2) второй не такой очевидный - при создании тестовых объектов руками часто во всех тестах используются одни и те же константы. Очевидные, типовые. Есть вероятность не попасть с выбранной тестовой константой в какие-то ветки тестовой логики, и т.об. не протестировать часть логики. Instancio же генерирует произвольные данные и может при очередном запуске помочь поймать редкую ошибку до того, как она проявится на ПРОМе.
Вот неплохая статья с примерами использования https://www.baeldung.com/java-test-data-instancio
#unittests #rare_test_libs
Baeldung
Generate Unit Test Data in Java Using Instancio | Baeldung
Learn how to eliminate manual data setup in tests by auto-generating the data using Instancio.
Всем привет!
Я уже писал про библиотеку с улучшенными assert-ами - Truth https://t.me/javaKotlinDevOps/51
Вот пример вывода ошибки в лог для такой банальной вещи, как проверка результата функции на true.
"Ванильный" JUnit 5:
java.lang.AssertionError
at org.junit.Assert.fail(Assert.java:87)
at org.junit.Assert.assertTrue(Assert.java:42)
at org.junit.Assert.assertTrue(Assert.java:53)
Понятно, что проверка не прошла, не понятно - где, пока не посмотришь код теста. Дело упрощается, если в методе один assertTrue, есть рекомендации так делать - один тест = одна проверка. Но это не всегда удобно. И в любом случае если код написан какое-то время назад - придется лезть в тест.
JUnit5 + Truth:
value of: needFilter(...)
expected to be true
Совсем другое дело!)))
#unittests
Я уже писал про библиотеку с улучшенными assert-ами - Truth https://t.me/javaKotlinDevOps/51
Вот пример вывода ошибки в лог для такой банальной вещи, как проверка результата функции на true.
"Ванильный" JUnit 5:
java.lang.AssertionError
at org.junit.Assert.fail(Assert.java:87)
at org.junit.Assert.assertTrue(Assert.java:42)
at org.junit.Assert.assertTrue(Assert.java:53)
Понятно, что проверка не прошла, не понятно - где, пока не посмотришь код теста. Дело упрощается, если в методе один assertTrue, есть рекомендации так делать - один тест = одна проверка. Но это не всегда удобно. И в любом случае если код написан какое-то время назад - придется лезть в тест.
JUnit5 + Truth:
value of: needFilter(...)
expected to be true
Совсем другое дело!)))
#unittests
Telegram
(java || kotlin) && devOps
Всем привет!
Не JUnit-ом единым...
Если говорить о фреймворках для unit тестирования все наверняка вспомнят JUnit и Mockito. Но есть ещё много чего полезного в этой области. Сегодня расскажу про библиотеки для улучшения читаемости проверок - assert. Про…
Не JUnit-ом единым...
Если говорить о фреймворках для unit тестирования все наверняка вспомнят JUnit и Mockito. Но есть ещё много чего полезного в этой области. Сегодня расскажу про библиотеки для улучшения читаемости проверок - assert. Про…
Всем привет!
Есть такой принцип - "безобразно, но единообразно". Если верить интернету, он появился в армии. Но может ли он быть применим к исходному коду и архитектуре?
Ответ - да.
Начну издалека - любой код и архитектура устаревает.
Но это не так страшно если выполняются три условия:
1) хорошо спроектированная гибкая архитектура. Я здесь про архитектуру уровня сервиса\АС. Маленькая ремарка - по отношению к коду термин архитектура плох тем, что в здании архитектуру изменить очень сложно. А в коде - можно.
2) высокое покрытие тестами. Тут все очевидно: много тестов - можно спокойно рефакторить
3) наличие времени для рефакторинга, т.е. другими словами - отсутствие вечного "это надо было сделать еще вчера"
В этом случае сервис можно отрефакторить для адаптации архитектуры к новым требованиям.
И делать это лучше сразу для всего приложения. Чтобы было "единообразно".
Почему так - я уже много об этом писал: изучать код - сложная задача. Один из способов облегчить ее - единообразие архитектуры, в т.ч. наименований, практики применения паттернов, разделения на уровни и модули.
Особенно сильно тяжесть изучения кода бьет по новичкам, но и для "старичков" спустя полгода-год код можно забыть.
В этом плане переделывать выглядящий "безобразно" код в отдельном методе или классе в рамках реализации текущей фичи - плохая идея, т.к. это ухудшит читаемость кода приложения в целом.
Лучше поговорить с бизнесом о рисках, зафиксировать техдолг, выделить на него отдельный технический релиз, согласовать время и все отрефакторить за раз.
Если вам кажется этот вариант фантастическим - понимаю, да, увы, такое бывает.
В этом случае предлагаю рефакторить максимально крупные куски кода вместе с бизнес-фичами. Ну например - модуль в терминах Maven или Gradle. Или набор модулей, относящийся к одному бизнес-процессу, если вы построили маленький монолитик. Или большой)
С монолитами, кстати, хуже всего - именно из-за устаревшей архитектуры, поменять которую разом невозможно, они зачастую и умирают.
При этом неплохо бы где-то рядом с кодом зафиксировать все архитектурные проблемы и план рефакторинга. В файлике типа todo.md в корне проекта. Точно не в wiki или в тикете, т.к. большинство разработчиков в wiki не пойдут.
Также подойдет JavaDoc. Часто он бывает тривиален и не несет ценности. Здесь же ситуация обратная.
Ну и конечно при ползучем рефакторинге поможет расстановка @Deprecated. На них ругается IDEA, SonarQube, они будут мозолить глаза. Это не гарантия того, что код будет поправлен, но уже что-то.
А лучший вариант - технический релиз. И не поосторожнее с монолитами)
P.S. Кстати, сложность рефакторинга является одним из возможных сигналов для разделения монолита на части
#arch #техдолг #refactoring #unittests
Есть такой принцип - "безобразно, но единообразно". Если верить интернету, он появился в армии. Но может ли он быть применим к исходному коду и архитектуре?
Ответ - да.
Начну издалека - любой код и архитектура устаревает.
Но это не так страшно если выполняются три условия:
1) хорошо спроектированная гибкая архитектура. Я здесь про архитектуру уровня сервиса\АС. Маленькая ремарка - по отношению к коду термин архитектура плох тем, что в здании архитектуру изменить очень сложно. А в коде - можно.
2) высокое покрытие тестами. Тут все очевидно: много тестов - можно спокойно рефакторить
3) наличие времени для рефакторинга, т.е. другими словами - отсутствие вечного "это надо было сделать еще вчера"
В этом случае сервис можно отрефакторить для адаптации архитектуры к новым требованиям.
И делать это лучше сразу для всего приложения. Чтобы было "единообразно".
Почему так - я уже много об этом писал: изучать код - сложная задача. Один из способов облегчить ее - единообразие архитектуры, в т.ч. наименований, практики применения паттернов, разделения на уровни и модули.
Особенно сильно тяжесть изучения кода бьет по новичкам, но и для "старичков" спустя полгода-год код можно забыть.
В этом плане переделывать выглядящий "безобразно" код в отдельном методе или классе в рамках реализации текущей фичи - плохая идея, т.к. это ухудшит читаемость кода приложения в целом.
Лучше поговорить с бизнесом о рисках, зафиксировать техдолг, выделить на него отдельный технический релиз, согласовать время и все отрефакторить за раз.
Если вам кажется этот вариант фантастическим - понимаю, да, увы, такое бывает.
В этом случае предлагаю рефакторить максимально крупные куски кода вместе с бизнес-фичами. Ну например - модуль в терминах Maven или Gradle. Или набор модулей, относящийся к одному бизнес-процессу, если вы построили маленький монолитик. Или большой)
С монолитами, кстати, хуже всего - именно из-за устаревшей архитектуры, поменять которую разом невозможно, они зачастую и умирают.
При этом неплохо бы где-то рядом с кодом зафиксировать все архитектурные проблемы и план рефакторинга. В файлике типа todo.md в корне проекта. Точно не в wiki или в тикете, т.к. большинство разработчиков в wiki не пойдут.
Также подойдет JavaDoc. Часто он бывает тривиален и не несет ценности. Здесь же ситуация обратная.
Ну и конечно при ползучем рефакторинге поможет расстановка @Deprecated. На них ругается IDEA, SonarQube, они будут мозолить глаза. Это не гарантия того, что код будет поправлен, но уже что-то.
А лучший вариант - технический релиз. И не поосторожнее с монолитами)
P.S. Кстати, сложность рефакторинга является одним из возможных сигналов для разделения монолита на части
#arch #техдолг #refactoring #unittests
Всем привет!
Я слишком ударился в архитектуру в последних постах, возвращаюсь с небес на землю)
Уже был пост как правильно писать unit тесты https://t.me/javaKotlinDevOps/33 и https://t.me/javaKotlinDevOps/43. Но кажется, что не хватает примеров. Начнем с антипаттернов - что не нужно делать в тестах?
Есть такая всем известная в Java мире библиотека для создания заглушек как Mockito. И у него есть среди всего прочего такая конструкция как
doNothing().when(mock).someMethod();
На первый взгляд полезная штука - говорим, что при вызове определенного метода в "заглушенном" объекте ничего делать не надо.
Но копнув чуть глубже понимаешь, что результатом вызова
val mock = Mockito.mock(SomeClass.class);
как раз и является объект, который ничего не делает.
А точнее метод объекта не вызывается, а там, где нужно вернуть результат - возвращаются некие значения по умолчанию: https://site.mockito.org/javadoc/current/org/mockito/Mockito.html#2
А если так - явно писать doNothing() - это усложнять код теста, его читаемость и ценность как документации к коду. Держать в уме, что все методы заглушки, которые не переопределены в тесте, не делают ничего - проще и понятнее, чем явно перечислять их.
Теперь уже метод doNothing() выглядит бесполезным. Зачем же его тогда добавляли в библиотеку?
Есть два кейса:
1) когда вы используете spy, а не mock. Напомню, главное отличие spy - он по умолчанию вызывает методы существующего объекта.
Пример:
List list = new LinkedList();
List spy = spy(list);
doNothing().when(spy).clear();
spy.someMethodWithCLeaCall();
2) и об этой возможности я, признаюсь, не знал - в одном выражении можно задать несколько ответов на ряд последовательных вызовов одного метода заглушки:
doNothing()
.doThrow(new RuntimeException())
.when(mock).someVoidMethod();
//первый раз ничего не делаем
mock.someVoidMethod();
//бросаем RuntimeException во второй раз
mock.someVoidMethod();
В следующих постах будет продолжение...
#unittests #mockito #antipatterns
Я слишком ударился в архитектуру в последних постах, возвращаюсь с небес на землю)
Уже был пост как правильно писать unit тесты https://t.me/javaKotlinDevOps/33 и https://t.me/javaKotlinDevOps/43. Но кажется, что не хватает примеров. Начнем с антипаттернов - что не нужно делать в тестах?
Есть такая всем известная в Java мире библиотека для создания заглушек как Mockito. И у него есть среди всего прочего такая конструкция как
doNothing().when(mock).someMethod();
На первый взгляд полезная штука - говорим, что при вызове определенного метода в "заглушенном" объекте ничего делать не надо.
Но копнув чуть глубже понимаешь, что результатом вызова
val mock = Mockito.mock(SomeClass.class);
как раз и является объект, который ничего не делает.
А точнее метод объекта не вызывается, а там, где нужно вернуть результат - возвращаются некие значения по умолчанию: https://site.mockito.org/javadoc/current/org/mockito/Mockito.html#2
А если так - явно писать doNothing() - это усложнять код теста, его читаемость и ценность как документации к коду. Держать в уме, что все методы заглушки, которые не переопределены в тесте, не делают ничего - проще и понятнее, чем явно перечислять их.
Теперь уже метод doNothing() выглядит бесполезным. Зачем же его тогда добавляли в библиотеку?
Есть два кейса:
1) когда вы используете spy, а не mock. Напомню, главное отличие spy - он по умолчанию вызывает методы существующего объекта.
Пример:
List list = new LinkedList();
List spy = spy(list);
doNothing().when(spy).clear();
spy.someMethodWithCLeaCall();
2) и об этой возможности я, признаюсь, не знал - в одном выражении можно задать несколько ответов на ряд последовательных вызовов одного метода заглушки:
doNothing()
.doThrow(new RuntimeException())
.when(mock).someVoidMethod();
//первый раз ничего не делаем
mock.someVoidMethod();
//бросаем RuntimeException во второй раз
mock.someVoidMethod();
В следующих постах будет продолжение...
#unittests #mockito #antipatterns
Telegram
(java || kotlin) && devOps
Всем привет!
Каким должен быть хороший тест?
Я в первую очередь про модульные (unit), но в принципе правила применимы к любым.
Основные моменты:
1) правило Arrange, Act, Assert https://xp123.com/articles/3a-arrange-act-assert/
Тест делится на три части:…
Каким должен быть хороший тест?
Я в первую очередь про модульные (unit), но в принципе правила применимы к любым.
Основные моменты:
1) правило Arrange, Act, Assert https://xp123.com/articles/3a-arrange-act-assert/
Тест делится на три части:…
Всем привет!
Продолжим про антипаттерны в unit тестах.
Мы можем проверить в тесте метод на то, что он выбрасывает исключение.
В JUnit 5:
assertThrows(MyException.class, () -> myObject.myFunction(myString), "Expected myFunction() to throw, but it didn't");
и с помощью AssertJ:
assertThatExceptionOfType(MyException.class)
.isThrownBy(() -> { myObject.myFunction(myString); })
.withMessage("%s!", "boom")
.withMessageContaining("boom")
.withNoCause();
Это хорошая проверка, т.к. всегда нужно убедится, что выброшено именно то исключение, которое мы ожидаем. А иногда требуется проверить его параметры.
Но есть еще методы проверки, что исключения не было.
В JUnit 5:
assertDoesNotThrow(() -> myObject.myFunction(myString));
и AssertJ:
assertThatNoException()
.isThrownBy(() -> myObject.myFunction(myString));
А вот эти проверки выглядят излишними, т.к. выбрасывание исключения в любом случае прерывает тест, и он подсвечивается красным.
Возможно, если тест сложный и в нем много проверок - хочется локализовать место ошибки. Но в этом случае я бы задумался про разделение теста на несколько вместо использования assertDoesNotThrow. Опять же, в противном случае придется использовать данную проверку во всех тестах, кроме тех, что выбрасывают исключение. А это IMHO "замусоривает" код.
P.S. Интересный момент - библиотека Truth, о которой я писал ранее https://t.me/javaKotlinDevOps/51, вообще не содержит методов проверки на выбрасывание исключения, предлагая пользоваться соответствующим методом из JUnit, ведь assertThrows, описанный ранее, возвращает исключение:
val exception = assertThrows(MyException.class, () -> myObject.myFunction());
А уже его можно проверить более детально:
assertThat(exception)
.hasCauseThat()
.isInstanceOf(NumberFormatException.class);
assertThat(exception)
.hasMessageThat()
.startsWith("Bad");
Мне этот подход нравится.
#unittests #junit #assertj #truth #antipatterns
Продолжим про антипаттерны в unit тестах.
Мы можем проверить в тесте метод на то, что он выбрасывает исключение.
В JUnit 5:
assertThrows(MyException.class, () -> myObject.myFunction(myString), "Expected myFunction() to throw, but it didn't");
и с помощью AssertJ:
assertThatExceptionOfType(MyException.class)
.isThrownBy(() -> { myObject.myFunction(myString); })
.withMessage("%s!", "boom")
.withMessageContaining("boom")
.withNoCause();
Это хорошая проверка, т.к. всегда нужно убедится, что выброшено именно то исключение, которое мы ожидаем. А иногда требуется проверить его параметры.
Но есть еще методы проверки, что исключения не было.
В JUnit 5:
assertDoesNotThrow(() -> myObject.myFunction(myString));
и AssertJ:
assertThatNoException()
.isThrownBy(() -> myObject.myFunction(myString));
А вот эти проверки выглядят излишними, т.к. выбрасывание исключения в любом случае прерывает тест, и он подсвечивается красным.
Возможно, если тест сложный и в нем много проверок - хочется локализовать место ошибки. Но в этом случае я бы задумался про разделение теста на несколько вместо использования assertDoesNotThrow. Опять же, в противном случае придется использовать данную проверку во всех тестах, кроме тех, что выбрасывают исключение. А это IMHO "замусоривает" код.
P.S. Интересный момент - библиотека Truth, о которой я писал ранее https://t.me/javaKotlinDevOps/51, вообще не содержит методов проверки на выбрасывание исключения, предлагая пользоваться соответствующим методом из JUnit, ведь assertThrows, описанный ранее, возвращает исключение:
val exception = assertThrows(MyException.class, () -> myObject.myFunction());
А уже его можно проверить более детально:
assertThat(exception)
.hasCauseThat()
.isInstanceOf(NumberFormatException.class);
assertThat(exception)
.hasMessageThat()
.startsWith("Bad");
Мне этот подход нравится.
#unittests #junit #assertj #truth #antipatterns
Telegram
(java || kotlin) && devOps
Всем привет!
Не JUnit-ом единым...
Если говорить о фреймворках для unit тестирования все наверняка вспомнят JUnit и Mockito. Но есть ещё много чего полезного в этой области. Сегодня расскажу про библиотеки для улучшения читаемости проверок - assert. Про…
Не JUnit-ом единым...
Если говорить о фреймворках для unit тестирования все наверняка вспомнят JUnit и Mockito. Но есть ещё много чего полезного в этой области. Сегодня расскажу про библиотеки для улучшения читаемости проверок - assert. Про…
Всем привет!
Еще один антипаттерн в unit тестах - чрезмерное увлечение verify.
List<String> mockedList = mock(MyList.class);
mockedList.size();
verify(mockedList, times(1)).size();
Да, штука полезная. Полезная по большому счету в 2 случаях:
1) проверка тестируемого метода в случае, когда он ничего не возвращает. К слову - еще одна альтернативная проверка: для mutable объекта и меняющего состояние метода проверить поля SUT (System under test).
2) проверка важных шагов алгоритма.
Если с void методом все понятно, то с деталями алгоритма главное - найти ту черту, которая отделяет суть алгоритма от деталей реализации. И проверять только первое. Т.к. в противном случае мы получим "хрупкий" тест, падающий при каждом изменении реализации. Собственно, от этого критерия можно и отталкиваться. Пример: если назначение данного сервисного класса вызов удаленной системы, отбрасывание метрик и логов - то именно эти вызовы и нужно проверить. В других случаях вызов метода логирования и отправку метрики я бы не проверял.
Еще хороший критерий - проверять те вызовы, которые описаны в аналитике.
Ясно, что 100% гарантии того, что тест проживет в неизменном виде долго и счастливо это не даст, мы же не провидцы) Но частота исправлений теста снизится.
Есть еще одна радикальная альтернатива - вообще не тестировать в рамках unit тестов методы, где все, что мы можем проверить - это вызывался тот или иной метод. А оставить это на интеграционные тесты.
#unittests #antipatterns #mockito
Еще один антипаттерн в unit тестах - чрезмерное увлечение verify.
List<String> mockedList = mock(MyList.class);
mockedList.size();
verify(mockedList, times(1)).size();
Да, штука полезная. Полезная по большому счету в 2 случаях:
1) проверка тестируемого метода в случае, когда он ничего не возвращает. К слову - еще одна альтернативная проверка: для mutable объекта и меняющего состояние метода проверить поля SUT (System under test).
2) проверка важных шагов алгоритма.
Если с void методом все понятно, то с деталями алгоритма главное - найти ту черту, которая отделяет суть алгоритма от деталей реализации. И проверять только первое. Т.к. в противном случае мы получим "хрупкий" тест, падающий при каждом изменении реализации. Собственно, от этого критерия можно и отталкиваться. Пример: если назначение данного сервисного класса вызов удаленной системы, отбрасывание метрик и логов - то именно эти вызовы и нужно проверить. В других случаях вызов метода логирования и отправку метрики я бы не проверял.
Еще хороший критерий - проверять те вызовы, которые описаны в аналитике.
Ясно, что 100% гарантии того, что тест проживет в неизменном виде долго и счастливо это не даст, мы же не провидцы) Но частота исправлений теста снизится.
Есть еще одна радикальная альтернатива - вообще не тестировать в рамках unit тестов методы, где все, что мы можем проверить - это вызывался тот или иной метод. А оставить это на интеграционные тесты.
#unittests #antipatterns #mockito
Всем привет!
Еще одна проблема, которую я замечаю в unit тестах - это засилье copy-paste. Тесты есть, покрытие хорошее, тесты проходят. Все хорошо?
Не совсем.
Как я уже писал тест - это самая лучшая документация к коду. Поэтому если в тесте есть mock объект, скопированный с другого теста, но на самом деле не нужный для успешного прохождения теста - это вводит в заблуждение при изучении проекта. Аналогично - если есть лишние проверки verify, которые конкретно у данного теста всегда успешны или не проверяют ничего полезного, зато приводят к хрупкости теста.
Резюме: тесты - это тоже код, к их написанию нужно относится с той же серьезностью, как и к коду.
#unittests #antipatterns
Еще одна проблема, которую я замечаю в unit тестах - это засилье copy-paste. Тесты есть, покрытие хорошее, тесты проходят. Все хорошо?
Не совсем.
Как я уже писал тест - это самая лучшая документация к коду. Поэтому если в тесте есть mock объект, скопированный с другого теста, но на самом деле не нужный для успешного прохождения теста - это вводит в заблуждение при изучении проекта. Аналогично - если есть лишние проверки verify, которые конкретно у данного теста всегда успешны или не проверяют ничего полезного, зато приводят к хрупкости теста.
Резюме: тесты - это тоже код, к их написанию нужно относится с той же серьезностью, как и к коду.
#unittests #antipatterns
Всем привет!
Продолжая тему рефакторинга. Основное предусловие для начала рефакторинга - это наличие хорошего тестового покрытия. Т.к. мы не меняем бизнес функционал, а улучшаем код либо для повышения производительности, либо для его упрощения. Но при этом нужно гарантировать, что бизнес функционал не сломался, т.е. не появились регрессионные баги. Ведь рефакторинг, в отличие от новой фичи, может затронуть все приложение. Соответственно, баги могут появиться в любом месте, и без тестового покрытия - это большие риски.
Можно рассмотреть похожий кейс. У нас есть монолит, мы хотим распилить его на микросервисы. Это тоже своего рода рефакторинг, только на уровне архитектурном уровне. И тоже аналогичное условие для его начала - наличие достаточного набора тестов. В данном случае повышается важность интеграционных тестов.
Важный момент: в процессе подготовки к разбиению монолита или серьёзному рефакторингу может возникнуть вопрос-предложение - а давайте все выкинем и напишем заново. Так вот - одним из базовых критериев для ответа на этот вопрос также является покрытие тестами. Очевидно, не единственным, но важным. Другие критерии - объем техдолга, соответствие текущей архитектуры и целевой.
Еще кейс - разработчик «боится» рефакторить код, т.к. он слишком сложный или затрагивает слишком много зависимостей. С тестами решиться на рефакторинг намного проще.
Вывод простой - пишите тесты, это страховка при рефакторинге)
#refactoring #unittests #microservices
Продолжая тему рефакторинга. Основное предусловие для начала рефакторинга - это наличие хорошего тестового покрытия. Т.к. мы не меняем бизнес функционал, а улучшаем код либо для повышения производительности, либо для его упрощения. Но при этом нужно гарантировать, что бизнес функционал не сломался, т.е. не появились регрессионные баги. Ведь рефакторинг, в отличие от новой фичи, может затронуть все приложение. Соответственно, баги могут появиться в любом месте, и без тестового покрытия - это большие риски.
Можно рассмотреть похожий кейс. У нас есть монолит, мы хотим распилить его на микросервисы. Это тоже своего рода рефакторинг, только на уровне архитектурном уровне. И тоже аналогичное условие для его начала - наличие достаточного набора тестов. В данном случае повышается важность интеграционных тестов.
Важный момент: в процессе подготовки к разбиению монолита или серьёзному рефакторингу может возникнуть вопрос-предложение - а давайте все выкинем и напишем заново. Так вот - одним из базовых критериев для ответа на этот вопрос также является покрытие тестами. Очевидно, не единственным, но важным. Другие критерии - объем техдолга, соответствие текущей архитектуры и целевой.
Еще кейс - разработчик «боится» рефакторить код, т.к. он слишком сложный или затрагивает слишком много зависимостей. С тестами решиться на рефакторинг намного проще.
Вывод простой - пишите тесты, это страховка при рефакторинге)
#refactoring #unittests #microservices
Всем привет!
Большинство Java разработчиков знают про Mockito - самый популярный фреймворк для создания заглушек. Но не ошибусь, если скажу, что большая часть разработчиков также не любит читать документацию) В т.ч. Mockito. А используемые инструменты надо знать.
Поэтому могу порекомендовать отличную статью про Mockito https://habr.com/ru/articles/444982/
Для затравки - из статьи можно узнать:
1) как определить - mock это или нет
2) в чем разница между when и do, когда все же нужен do
3) как одной лямбдой сделать сложную проверку входных параметров в конструкции when
4) как проверить порядок вызова методов
5) про сессии Mockito
и много других интересных особенностей фреймворка.
#mockito #java #unittests
Большинство Java разработчиков знают про Mockito - самый популярный фреймворк для создания заглушек. Но не ошибусь, если скажу, что большая часть разработчиков также не любит читать документацию) В т.ч. Mockito. А используемые инструменты надо знать.
Поэтому могу порекомендовать отличную статью про Mockito https://habr.com/ru/articles/444982/
Для затравки - из статьи можно узнать:
1) как определить - mock это или нет
2) в чем разница между when и do, когда все же нужен do
3) как одной лямбдой сделать сложную проверку входных параметров в конструкции when
4) как проверить порядок вызова методов
5) про сессии Mockito
и много других интересных особенностей фреймворка.
#mockito #java #unittests
Хабр
Mockito и как его готовить
О статье Перед вами очередное руководство по Mockito. В нём я, с одной стороны, попытался описать функционал этой библиотеки так, чтобы незнакомый с нею читатель сразу получил возможность полноценно...
Всем привет!
Недавно набрел на интересную статью - https://enterprisecraftsmanship.com/posts/domain-model-purity-completeness
Настоятельно рекомендую ее прочитать, но как всегда вкратце перескажу)
В крупную клетку любой нормально спроектированный сервис включает в себя контроллер (порт в терминологии гексагональной архитекторы), модель и адаптеры.
Вся логика должна быть в модели. Но что делать, если предварительная проверка данных требует обращения к внешней системе (БД), и при этом построение запроса для проверки - это тоже бизнес-логика.
Варианта предлагается три:
1) внести ее в модель, скрыть обращение к данным за интерфейсами, но в любом случае в итоге наше ядро (модель) лезет в БД, что плохо. В первую очередь плохо концептуально, а если "спустится на землю" - сложнее писать модульные тесты, увеличиваются риски "загрязнения" ядра. Т.е. следующие поколения разработчиков видя, что из модели вызывается СУБД, скажут - а что, так можно было?) И будут тянуть в модель другие внешние зависимости. Теория еще такая есть, разбитых окон. К слову - автор статьи также автор отличной книги о модульном тестировании, я о ней уже писал https://t.me/javaKotlinDevOps/50, возможно поэтому ему данный вариант не нравится
2) оставить часть логики в контроллере. Но тогда получается, что логика размазана по двум слоям
3) заранее загрузить нужные данные в ядро. Допустимо, но только для каких-то маленьких и редко меняющихся справочников, типа регионов. Т.е. только в отдельных случаях.
В итоге у нас компромисс между полнотой модели, строгостью соблюдения архитектурных принципов и скоростью работы.
Что тут интересно - главная идея статьи не о том, как сделать правильно, а про то, что разработка ПО - это искусство компромиссов.
Теорема CAP, упоминаемая в статье, к слову о том же. Единственного правильного для всех случаев жизни решения нет. Увы(
Еще одно дополнение. В теории возможен еще один вариант, расширяющий пространство компромиссов. Он немного "наркоманский" в том плане, что усложняет систему.
Предположим мы сделаем интерфейс валидатора в модели. Правила валидации будет задавать декларативно и хранить в модели, не привязывая их к конкретному хранилищу. Т.е. код контроллера вместо:
validateLogic()
callModel()
превращается в:
val rules = getValidateRulesFromModel()
val request = buildValidateRequest()
validate(request)
callModel()
Сам же и покритикую. Задача вроде решена, но дополнительного кода потребуется много. Второй минус, тоже важный - последовательность вызовов неочевидна. Если новый разработчик придет в команду - очень может быть он скажет "WTF" и захочет переписать. Как решение этой проблемы могу предложить описывать алгоритм в документации к коду или аналитике. Документация не нужна с "говорящим" кодом, но тут как раз исключение. Но сложность понимания кода в любом случае повышается.
Т.об. в пространство компромиссов мы вводим еще один параметр - сложность. Полнота модели, целостность архитектуры, скорость и сложность.
#arch #unittests #dev_compromises
Недавно набрел на интересную статью - https://enterprisecraftsmanship.com/posts/domain-model-purity-completeness
Настоятельно рекомендую ее прочитать, но как всегда вкратце перескажу)
В крупную клетку любой нормально спроектированный сервис включает в себя контроллер (порт в терминологии гексагональной архитекторы), модель и адаптеры.
Вся логика должна быть в модели. Но что делать, если предварительная проверка данных требует обращения к внешней системе (БД), и при этом построение запроса для проверки - это тоже бизнес-логика.
Варианта предлагается три:
1) внести ее в модель, скрыть обращение к данным за интерфейсами, но в любом случае в итоге наше ядро (модель) лезет в БД, что плохо. В первую очередь плохо концептуально, а если "спустится на землю" - сложнее писать модульные тесты, увеличиваются риски "загрязнения" ядра. Т.е. следующие поколения разработчиков видя, что из модели вызывается СУБД, скажут - а что, так можно было?) И будут тянуть в модель другие внешние зависимости. Теория еще такая есть, разбитых окон. К слову - автор статьи также автор отличной книги о модульном тестировании, я о ней уже писал https://t.me/javaKotlinDevOps/50, возможно поэтому ему данный вариант не нравится
2) оставить часть логики в контроллере. Но тогда получается, что логика размазана по двум слоям
3) заранее загрузить нужные данные в ядро. Допустимо, но только для каких-то маленьких и редко меняющихся справочников, типа регионов. Т.е. только в отдельных случаях.
В итоге у нас компромисс между полнотой модели, строгостью соблюдения архитектурных принципов и скоростью работы.
Что тут интересно - главная идея статьи не о том, как сделать правильно, а про то, что разработка ПО - это искусство компромиссов.
Теорема CAP, упоминаемая в статье, к слову о том же. Единственного правильного для всех случаев жизни решения нет. Увы(
Еще одно дополнение. В теории возможен еще один вариант, расширяющий пространство компромиссов. Он немного "наркоманский" в том плане, что усложняет систему.
Предположим мы сделаем интерфейс валидатора в модели. Правила валидации будет задавать декларативно и хранить в модели, не привязывая их к конкретному хранилищу. Т.е. код контроллера вместо:
validateLogic()
callModel()
превращается в:
val rules = getValidateRulesFromModel()
val request = buildValidateRequest()
validate(request)
callModel()
Сам же и покритикую. Задача вроде решена, но дополнительного кода потребуется много. Второй минус, тоже важный - последовательность вызовов неочевидна. Если новый разработчик придет в команду - очень может быть он скажет "WTF" и захочет переписать. Как решение этой проблемы могу предложить описывать алгоритм в документации к коду или аналитике. Документация не нужна с "говорящим" кодом, но тут как раз исключение. Но сложность понимания кода в любом случае повышается.
Т.об. в пространство компромиссов мы вводим еще один параметр - сложность. Полнота модели, целостность архитектуры, скорость и сложность.
#arch #unittests #dev_compromises
Enterprise Craftsmanship
Domain model purity vs. domain model completeness (DDD Trilemma)
I’ve been meaning to write this article for a long time and, finally, here it is: the topic of domain model purity versus domain model completeness.
Всем привет!
Нашел хорошую статью о том, как совместить тестирование Spring контроллеров и один из самых известных фреймворков для тестирования REST - Rest Assured. https://www.baeldung.com/spring-mock-mvc-rest-assured
Кстати, в начале статьи есть ссылка на пример использования чистого Spring MVC Test, если кто его не использовал - можете сравнить синтаксис.
Еще статья хороша тем, что четко разделяет модульные и интеграционные тесты. И я бы разделил точно также) Я иногда задаю вопрос о видах тестов на интервью, ответ мне не всегда нравится. Для ленивых, вкратце - интеграционным тест можно считать, если появляется сеть - открывается порт, вызывается другой процесс, внешнее хранилище, пусть даже и в embedded варианте. Хотя справедливости ради - вопрос холиварный, из-за того, что много пограничных случаев.
#unittests #spring #rest #integration_tests #interview_question
Нашел хорошую статью о том, как совместить тестирование Spring контроллеров и один из самых известных фреймворков для тестирования REST - Rest Assured. https://www.baeldung.com/spring-mock-mvc-rest-assured
Кстати, в начале статьи есть ссылка на пример использования чистого Spring MVC Test, если кто его не использовал - можете сравнить синтаксис.
Еще статья хороша тем, что четко разделяет модульные и интеграционные тесты. И я бы разделил точно также) Я иногда задаю вопрос о видах тестов на интервью, ответ мне не всегда нравится. Для ленивых, вкратце - интеграционным тест можно считать, если появляется сеть - открывается порт, вызывается другой процесс, внешнее хранилище, пусть даже и в embedded варианте. Хотя справедливости ради - вопрос холиварный, из-за того, что много пограничных случаев.
#unittests #spring #rest #integration_tests #interview_question
Baeldung
REST-assured Support for Spring MockMvc | Baeldung
Learn how to test Spring REST controllers using the RestAssuredMockMvc API from REST-assured.
Всем привет!
Как приучить себя писать модульные, они же unit тесты?
1) начинать лучше на новом проекте. Основная проблема с тестами в том, что часто писать тесты сложно - приходится создавать много моков, возможно рефакторить код. На новом проекте проще изначально начать писать тестируемый код. Особенно хорошо изначально тестируемый код получается писать используя TDD. А потом, когда втянешься - можно дописать тесты и для legacy)
2) изначально определить, что обязательно должно быть покрыто тестами, остальное исключить из контроля покрытия. Если конечно у вас подключен контроль покрытия) Ничто так не демотивирует, как написание ненужных тестов на тривиальный код
3) заставить себя написать первые скажем 100 тестов. Или 200. После того, как ключевые классы с бизнес-логикой будут покрыты тестами, появляется уверенность при рефакторинге кода. Правишь что-то, запускаешь тесты, убеждаешься, что ничего не сломал. Возвращаешься к коду спустя полгода, что-то правишь и снова все ок. Это одно из самых крутых свойств нормального покрытия кода тестами. Но для начала этого уровня покрытия нужно достичь, т.е. первое время видимого эффекта от тестов не будет.
4) переписать медленные тесты, разделить быстрые и медленные. Как правило это разделение совпадет с модульные-интеграционные. Модульные запускать как можно чаще. Причина - ожидание 5 минут ну когда же закончатся тесты - бесит
5) самый смешной пункт - включить в IDEA отображение успешно пройденных тестов. Сотня зеленых галочек... успокаивает что ли))))
#unittests #TDD
Как приучить себя писать модульные, они же unit тесты?
1) начинать лучше на новом проекте. Основная проблема с тестами в том, что часто писать тесты сложно - приходится создавать много моков, возможно рефакторить код. На новом проекте проще изначально начать писать тестируемый код. Особенно хорошо изначально тестируемый код получается писать используя TDD. А потом, когда втянешься - можно дописать тесты и для legacy)
2) изначально определить, что обязательно должно быть покрыто тестами, остальное исключить из контроля покрытия. Если конечно у вас подключен контроль покрытия) Ничто так не демотивирует, как написание ненужных тестов на тривиальный код
3) заставить себя написать первые скажем 100 тестов. Или 200. После того, как ключевые классы с бизнес-логикой будут покрыты тестами, появляется уверенность при рефакторинге кода. Правишь что-то, запускаешь тесты, убеждаешься, что ничего не сломал. Возвращаешься к коду спустя полгода, что-то правишь и снова все ок. Это одно из самых крутых свойств нормального покрытия кода тестами. Но для начала этого уровня покрытия нужно достичь, т.е. первое время видимого эффекта от тестов не будет.
4) переписать медленные тесты, разделить быстрые и медленные. Как правило это разделение совпадет с модульные-интеграционные. Модульные запускать как можно чаще. Причина - ожидание 5 минут ну когда же закончатся тесты - бесит
5) самый смешной пункт - включить в IDEA отображение успешно пройденных тестов. Сотня зеленых галочек... успокаивает что ли))))
#unittests #TDD
Всем привет!
Продолжим про тестовые библиотеки. Достаточно часто возникает задача проверить в тесте структурированные текстовые данные. Я про XML, json и yaml как самые распространённые варианты. XML первым указан по старшинству, а не распространённости) И он пока ещё жив.
Зачем для этого нужно специализированная библиотека:
1) текст может отличаться формированием - пробелами и переносами строк. Иногда это важно при сравнении, но часто нужно проигнорировать
2) могут быть «лишние» тэги/атрибуты - которые не должны участвовать в сравнении
3) может отличаться порядок тэгов
4) может стоят задача проверить только отдельные элементы в дереве, или их количество
Одной библиотеки для всех форматов нет, но есть две - XMLUnit и JsonAssert. Так думал я, пока не начал копать тему глубже. И искать, чем же можно проверить yaml. Оказывается, есть «более лучшая» замена JsonAssert, которая:
1) умеет и в json, и в yaml, причём может сравнивать json и yaml. И также сравнивать с Java обьектом
2) умеет все это делать в стиле Assert/Truth, он же method chaining. А это облегчает как написание условий проверки благодаря auto competition в IDE, так и их чтение. А возможно в некоторых случаях позволит отказаться от BDD фреймворка.
3) прозрачно работает с разными источниками данных - строка и файл
Встречайте - ModelAssert https://github.com/webcompere/model-assert
И более подробно https://www.baeldung.com/json-modelassert
Что интересно - автор сам написал статью на baeldung и ссылается на неё в документации.
Ещё важный момент - подчеркивается совместимость библиотеки со Spring MockMvc и Mockito. Возможно даже ради этой поддержки автор и запилил совместимость с Hamcrest. И последнее - отдельно продумано тестирование json с guid. Во-первых можно игнорировать различия конкретных значений uuid (они могут генерироваться в каждом тесте), во-вторых легко написать проверку формата uuid.
А что же XML - вот хорошая статья по XMLUnit https://www.baeldung.com/xmlunit2
Он может примерно то же самое, но без method chaining. Но зато с валидацией по схеме (xsd). Что кстати неплохо характеризует отличия в подходах к работе с XML и json)
#unittests #rare_test_libs #XML #json #yaml
Продолжим про тестовые библиотеки. Достаточно часто возникает задача проверить в тесте структурированные текстовые данные. Я про XML, json и yaml как самые распространённые варианты. XML первым указан по старшинству, а не распространённости) И он пока ещё жив.
Зачем для этого нужно специализированная библиотека:
1) текст может отличаться формированием - пробелами и переносами строк. Иногда это важно при сравнении, но часто нужно проигнорировать
2) могут быть «лишние» тэги/атрибуты - которые не должны участвовать в сравнении
3) может отличаться порядок тэгов
4) может стоят задача проверить только отдельные элементы в дереве, или их количество
Одной библиотеки для всех форматов нет, но есть две - XMLUnit и JsonAssert. Так думал я, пока не начал копать тему глубже. И искать, чем же можно проверить yaml. Оказывается, есть «более лучшая» замена JsonAssert, которая:
1) умеет и в json, и в yaml, причём может сравнивать json и yaml. И также сравнивать с Java обьектом
2) умеет все это делать в стиле Assert/Truth, он же method chaining. А это облегчает как написание условий проверки благодаря auto competition в IDE, так и их чтение. А возможно в некоторых случаях позволит отказаться от BDD фреймворка.
3) прозрачно работает с разными источниками данных - строка и файл
Встречайте - ModelAssert https://github.com/webcompere/model-assert
И более подробно https://www.baeldung.com/json-modelassert
Что интересно - автор сам написал статью на baeldung и ссылается на неё в документации.
Ещё важный момент - подчеркивается совместимость библиотеки со Spring MockMvc и Mockito. Возможно даже ради этой поддержки автор и запилил совместимость с Hamcrest. И последнее - отдельно продумано тестирование json с guid. Во-первых можно игнорировать различия конкретных значений uuid (они могут генерироваться в каждом тесте), во-вторых легко написать проверку формата uuid.
А что же XML - вот хорошая статья по XMLUnit https://www.baeldung.com/xmlunit2
Он может примерно то же самое, но без method chaining. Но зато с валидацией по схеме (xsd). Что кстати неплохо характеризует отличия в подходах к работе с XML и json)
#unittests #rare_test_libs #XML #json #yaml
GitHub
GitHub - webcompere/model-assert: Assertions for data models
Assertions for data models. Contribute to webcompere/model-assert development by creating an account on GitHub.
Всем привет!
Продолжим тему тестирования http взаимодействий. Кроме http клиента приложение может выставлять API. Этот пост про REST API, как наиболее распространённое. В мире победившего Spring endpoint обычно создаётся с помощью Spring @RestController, но в теории может быть и JAX-RS контроллер или что-то другое, даже «голый» сервлет.
Можно рассмотреть 4 случая.
1) модульное тестирование слоя контроллеров Spring приложения. В этом случае все сервисы в контроллере «мокаются», а Spring context загружается в ограниченном объёме, необходимом для эмуляции контроллера. Это кстати крутая фишка Spring - эмуляция контроллера без реального сетевого взаимодействия для модульных тестов. Ключевые слова - MockMvc и @WebMvcTest. Хорошее описание тут: https://sysout.ru/testirovanie-kontrollerov-s-pomoshhyu-mockmvc/, надо смотреть п. 2
Как я уже писал ранее - встроенные assert-ы MockMvc для json можно при желании заменить на ModelAssert, см. мой пост https://t.me/javaKotlinDevOps/343
2) интеграционное тестирование всех слоёв Spring приложения, но без сети. Вообще говоря, что считать интеграционным тестом - отдельная большая тема, но в большинстве статей, что я видел, такие тесты называются интеграционными. Используется тот же MockMvc, но уже с @SpringBootTest. Основное отличие - загружается весь Spring Context. Поэтому тест медленнее, чем больше приложение, тем медленнее. Если надо - в контексте можно поменять бины для тестового режима через Spring Profiles. Пример такого текста - в статье выше см. п. 1
3) интеграционное тестирование Spring приложения с сетью. Используется @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) и TestRestTemplate. Сервер для тестов запускается на произвольном порту, порт можно получить в тесте. Тесты этого типа ещё медленнее. Когда может быть полезен - нужно приближённое «к бою» тестирование. Или какие фишки http протестировать, типа редиректа. Вот пример https://sysout.ru/testirovanie-spring-boot-prilozheniya-s-testresttemplate/
А вот сравнение всех 3 подходов https://www.baeldung.com/spring-mockmvc-vs-webmvctest
4) у нас не Spring контроллер. Или уже есть опыт использования RestAssured, и он сугубо позитивный) Или нужны какие-то фишки RestAssured. А их много - xsd/json валидация, проверка длительности выполнения, встроенное логирование запроса и ответа, в т.ч. условное - в случае ошибки, использование Groovy для получения данных из тела запроса (!). Также данная библиотека рекомендуется для BDD тестов, т.к придерживается принятой там терминологии Given-When-Then https://en.m.wikipedia.org/wiki/Given-When-Then Для выполнения теста приложение нужно вначале запустить, например, через фазу pre-integration-test цикла сборки Maven. Вот неплохой tutorial https://www.baeldung.com/rest-assured-tutorial
Итого - как всегда в Java есть хорошие инструменты для разных задач.
#unittests #integration_test #rare_test_libs
Продолжим тему тестирования http взаимодействий. Кроме http клиента приложение может выставлять API. Этот пост про REST API, как наиболее распространённое. В мире победившего Spring endpoint обычно создаётся с помощью Spring @RestController, но в теории может быть и JAX-RS контроллер или что-то другое, даже «голый» сервлет.
Можно рассмотреть 4 случая.
1) модульное тестирование слоя контроллеров Spring приложения. В этом случае все сервисы в контроллере «мокаются», а Spring context загружается в ограниченном объёме, необходимом для эмуляции контроллера. Это кстати крутая фишка Spring - эмуляция контроллера без реального сетевого взаимодействия для модульных тестов. Ключевые слова - MockMvc и @WebMvcTest. Хорошее описание тут: https://sysout.ru/testirovanie-kontrollerov-s-pomoshhyu-mockmvc/, надо смотреть п. 2
Как я уже писал ранее - встроенные assert-ы MockMvc для json можно при желании заменить на ModelAssert, см. мой пост https://t.me/javaKotlinDevOps/343
2) интеграционное тестирование всех слоёв Spring приложения, но без сети. Вообще говоря, что считать интеграционным тестом - отдельная большая тема, но в большинстве статей, что я видел, такие тесты называются интеграционными. Используется тот же MockMvc, но уже с @SpringBootTest. Основное отличие - загружается весь Spring Context. Поэтому тест медленнее, чем больше приложение, тем медленнее. Если надо - в контексте можно поменять бины для тестового режима через Spring Profiles. Пример такого текста - в статье выше см. п. 1
3) интеграционное тестирование Spring приложения с сетью. Используется @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) и TestRestTemplate. Сервер для тестов запускается на произвольном порту, порт можно получить в тесте. Тесты этого типа ещё медленнее. Когда может быть полезен - нужно приближённое «к бою» тестирование. Или какие фишки http протестировать, типа редиректа. Вот пример https://sysout.ru/testirovanie-spring-boot-prilozheniya-s-testresttemplate/
А вот сравнение всех 3 подходов https://www.baeldung.com/spring-mockmvc-vs-webmvctest
4) у нас не Spring контроллер. Или уже есть опыт использования RestAssured, и он сугубо позитивный) Или нужны какие-то фишки RestAssured. А их много - xsd/json валидация, проверка длительности выполнения, встроенное логирование запроса и ответа, в т.ч. условное - в случае ошибки, использование Groovy для получения данных из тела запроса (!). Также данная библиотека рекомендуется для BDD тестов, т.к придерживается принятой там терминологии Given-When-Then https://en.m.wikipedia.org/wiki/Given-When-Then Для выполнения теста приложение нужно вначале запустить, например, через фазу pre-integration-test цикла сборки Maven. Вот неплохой tutorial https://www.baeldung.com/rest-assured-tutorial
Итого - как всегда в Java есть хорошие инструменты для разных задач.
#unittests #integration_test #rare_test_libs
SYSOUT
Тестирование контроллеров с помощью MockMvc - SYSOUT
Класс MockMvc предназначен для тестирования контроллеров. Он позволяет тестировать контроллеры без запуска http-сервера. То есть при выполнении тестов сетевое соединение не создается. С MockMvc можно писать как интеграционные тесты, так и unit-тесты. Ниже…
Всем привет!
Небольшое дополнение про Given When Then из предыдущего поста. Данная формулировка пошла из BDD тестирования - Behavior driven development. BDD - это приемочное тестирование. Но в модульных (юнит) тестах существует аналогичная концепция - AAA - Arrange - Act - Assert. Суть та же - тест делится на 3 этапа: подготовка данных - вызов тестируемого метода - проверка результата.
Обычно эти три части в тесте разделяются пустой строкой для читаемости. В случае RestAssured и method chainig так не выйдет, но стоит разделить этапы переводом на новую строку. Arrange опционален, Act и Assert - обязательны. Act и Assert стоит разделить, даже если тянется рука сразу проверить результат вызова метода.
#unittests
Небольшое дополнение про Given When Then из предыдущего поста. Данная формулировка пошла из BDD тестирования - Behavior driven development. BDD - это приемочное тестирование. Но в модульных (юнит) тестах существует аналогичная концепция - AAA - Arrange - Act - Assert. Суть та же - тест делится на 3 этапа: подготовка данных - вызов тестируемого метода - проверка результата.
Обычно эти три части в тесте разделяются пустой строкой для читаемости. В случае RestAssured и method chainig так не выйдет, но стоит разделить этапы переводом на новую строку. Arrange опционален, Act и Assert - обязательны. Act и Assert стоит разделить, даже если тянется рука сразу проверить результат вызова метода.
#unittests