Forwarded from javaswag
https://javaswag.github.io/episode/72/
Слушать подкаст в Apple | Spotify | Yandex
В 72 выпуске подкаста Javaswag поговорили с Александром Барминым о Спринге и архитектуре Необанка
00:00 Начало
05:34 Значение доменной области в разработке
17:28 IBM FileNet и Java EE
22:45 Проблемы и эволюция Java EE
32:50 Spring и Spring Boot
48:10 Миграция между версиями Spring
56:05 Гибкость и сложности Spring Boot
01:01:02 Адаптация Spring к современным трендам
01:04:50 Проблемы зависимости от Spring
01:07:10 Конкуренция и эволюция Spring
01:14:49 Kotlin и Spring: синергия технологий
01:15:44 Эволюция TransferWise в Neobank
01:16:36 Архитектура Wise: микросервисы и AWS
01:19:21 Kubernetes и проблемы распределенных систем
01:24:55 Консистентность и механизмы реконсиляции
01:29:08 Управление микросервисами и версиями
01:33:20 Автоматизация обновления зависимостей
01:37:07 CI/CD и миграции баз данных
01:41:17 Деплой
01:46:49 Непопулярное мнение о языках программирования
01:50:00 Критика Spring Boot и его магии
Гость https://www.linkedin.com/in/abarmin/
Ссылки:
Канал Александра на YouTube - https://www.youtube.com/@ABarmin
Канал Java & Spring Weekly в Telegram - @java_weekly
Wise Tech Stack - https://medium.com/wise-engineering/wise-tech-stack-2022-edition-a6ac089a382f
Spring Cloud с Борисовым - https://youtu.be/4tSyz_v9w7Q
Кип сейф! 🖖
Слушать подкаст в Apple | Spotify | Yandex
В 72 выпуске подкаста Javaswag поговорили с Александром Барминым о Спринге и архитектуре Необанка
00:00 Начало
05:34 Значение доменной области в разработке
17:28 IBM FileNet и Java EE
22:45 Проблемы и эволюция Java EE
32:50 Spring и Spring Boot
48:10 Миграция между версиями Spring
56:05 Гибкость и сложности Spring Boot
01:01:02 Адаптация Spring к современным трендам
01:04:50 Проблемы зависимости от Spring
01:07:10 Конкуренция и эволюция Spring
01:14:49 Kotlin и Spring: синергия технологий
01:15:44 Эволюция TransferWise в Neobank
01:16:36 Архитектура Wise: микросервисы и AWS
01:19:21 Kubernetes и проблемы распределенных систем
01:24:55 Консистентность и механизмы реконсиляции
01:29:08 Управление микросервисами и версиями
01:33:20 Автоматизация обновления зависимостей
01:37:07 CI/CD и миграции баз данных
01:41:17 Деплой
01:46:49 Непопулярное мнение о языках программирования
01:50:00 Критика Spring Boot и его магии
Гость https://www.linkedin.com/in/abarmin/
Ссылки:
Канал Александра на YouTube - https://www.youtube.com/@ABarmin
Канал Java & Spring Weekly в Telegram - @java_weekly
Wise Tech Stack - https://medium.com/wise-engineering/wise-tech-stack-2022-edition-a6ac089a382f
Spring Cloud с Борисовым - https://youtu.be/4tSyz_v9w7Q
Кип сейф! 🖖
Javaswag
#72 - Александр Бармин - эволюция Спринга и архитектура Необанка
В 72 выпуске подкаста Javaswag поговорили с Александром Барминым о Спринге и архитектуре Необанка
🔥12
Jump Game II - LeetCode with me 44
Задача на динамическое программирование. Иногда кажется, что самые быстрые алгоритмы на LeetCode это далеко не самые правильные или подходящие, поэтому рассмотрим сначала хороший алгоритм, а потом быстрый.
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Задача на динамическое программирование. Иногда кажется, что самые быстрые алгоритмы на LeetCode это далеко не самые правильные или подходящие, поэтому рассмотрим сначала хороший алгоритм, а потом быстрый.
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Media is too big
VIEW IN TELEGRAM
🔥6
Permutations - LeetCode with me 45
Сегодня рассмотрим две задачи на генерацию перестановок. Обе решаются сходным образом - с использованием алгоритма backtracking.
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Сегодня рассмотрим две задачи на генерацию перестановок. Обе решаются сходным образом - с использованием алгоритма backtracking.
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Media is too big
VIEW IN TELEGRAM
👍7❤🔥2
Rotate Image - LeetCode with me 46
Задача на поворот двухмерного изображения. Берем и поворачиваем.
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Задача на поворот двухмерного изображения. Берем и поворачиваем.
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Media is too big
VIEW IN TELEGRAM
❤5
Group Anagrams - LeetCode with me 47
Группируем анаграммы вместе. Анаграммы - слова, которые могут быть преобразованы друг в друга путем перестановки букв в них. Очевидное решение с сортировкой работает, но не дает достаточной производительности, поэтому напишем свой несложный алгоритм хеширования.
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Группируем анаграммы вместе. Анаграммы - слова, которые могут быть преобразованы друг в друга путем перестановки букв в них. Очевидное решение с сортировкой работает, но не дает достаточной производительности, поэтому напишем свой несложный алгоритм хеширования.
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Media is too big
VIEW IN TELEGRAM
👍9❤4
Pow x ^ n - LeetCode with me 48
Реализуем функцию возведения в степень без использования стандартной библиотеки. Для повышения производительности воспользуемся знаниями из школьного курса алгебры.
Это последнее, семидесятое видео в 2024 году. За весь год мои видео посмотрели более 61 тысячи раз, что составляет более 8 тысяч часов. А еще на канал на YouTube подписалось больше 1100 новых зрителей. Довольно неплохой рост, на мой взгляд!
Спасибо всем, кто был со мной в 2024 году, увидимся в новом, 2025 году!
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Реализуем функцию возведения в степень без использования стандартной библиотеки. Для повышения производительности воспользуемся знаниями из школьного курса алгебры.
Это последнее, семидесятое видео в 2024 году. За весь год мои видео посмотрели более 61 тысячи раз, что составляет более 8 тысяч часов. А еще на канал на YouTube подписалось больше 1100 новых зрителей. Довольно неплохой рост, на мой взгляд!
Спасибо всем, кто был со мной в 2024 году, увидимся в новом, 2025 году!
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Media is too big
VIEW IN TELEGRAM
👍10❤3
Уважаемые читатели, поздравляю вас с наступающим Новым годом!🥳
Желаю в новом году решать побольше интересных задач, не терять мотивации и оптимизма.
А еще смотрите мои видео и пишите побольше комментариев 👨💻👩💻
С праздником!🎉
Желаю в новом году решать побольше интересных задач, не терять мотивации и оптимизма.
А еще смотрите мои видео и пишите побольше комментариев 👨💻👩💻
С праздником!
Please open Telegram to view this post
VIEW IN TELEGRAM
🥰15🔥9🎄6🤝2
N-Queens - LeetCode with me 49
Первая задача в новом 2025 году. Начнем с уровня Hard и расставим ферзей по шахматной доске. Ничего сложного, обычный backtracking.
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Первая задача в новом 2025 году. Начнем с уровня Hard и расставим ферзей по шахматной доске. Ничего сложного, обычный backtracking.
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Media is too big
VIEW IN TELEGRAM
❤4
Maximum Subarray - LeetCode with me 50
Ищем подмассив произвольной длинный с максимальной суммой. Задача решается с использованием скользящего окна или алгоритма Кадано. Также разберем рекурсивный подход, основанный на идее divide and conquer.
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Ищем подмассив произвольной длинный с максимальной суммой. Задача решается с использованием скользящего окна или алгоритма Кадано. Также разберем рекурсивный подход, основанный на идее divide and conquer.
Ссылки: Ссылка на задачу | Patreon (если вы не в России) | Boosty (если вы в России) | #LeetCode
Media is too big
VIEW IN TELEGRAM
❤7✍2
Spring Shorts №1 - Embedded Database
Долго думал, как продолжить, с каким материалом вернуться. С одной стороны, старые выпуски были, на мой взгляд, очень полезны - в них я разбирал внутреннее устройство Spring Framework. С другой стороны, возможно, мой темп подходит не всем - слишком медленно или слишком быстро, не видно весь код сразу, нет возможности быстро вернуться к нужному фрагменту и так далее. Попробую немного другой формат - текстовый, назову его Spring Shorts, а сегодня как раз первый выпуск.
Наверняка, многим известно, что если в Spring Boot добавить в качестве зависимости H2, то, по умолчанию, Spring Boot запустит H2 в памяти. Инстанс H2 можно настроить с помощью JDBC URL, который указывается в параметре
Кроме этого, в самом Spring Framework есть возможность создавать in-memory инстансы не только H2, но также HSQL и Derby. Для этого нужно добавить соответствующую зависимость и объявить бин
Зачем может понадобиться отдельный
Долго думал, как продолжить, с каким материалом вернуться. С одной стороны, старые выпуски были, на мой взгляд, очень полезны - в них я разбирал внутреннее устройство Spring Framework. С другой стороны, возможно, мой темп подходит не всем - слишком медленно или слишком быстро, не видно весь код сразу, нет возможности быстро вернуться к нужному фрагменту и так далее. Попробую немного другой формат - текстовый, назову его Spring Shorts, а сегодня как раз первый выпуск.
Наверняка, многим известно, что если в Spring Boot добавить в качестве зависимости H2, то, по умолчанию, Spring Boot запустит H2 в памяти. Инстанс H2 можно настроить с помощью JDBC URL, который указывается в параметре
spring.datasource.url в application.yml или application.properties. Если URL формата jdbc:h2:mem:test, то база данных будет запущена в памяти и все данные будут пропадать каждый раз, когда приложение перезапускается. Если же URL вида jdbc:h2:/data/test, то данные будут храниться на диске и сохраняться между перезапусками приложения. Кроме этого, в самом Spring Framework есть возможность создавать in-memory инстансы не только H2, но также HSQL и Derby. Для этого нужно добавить соответствующую зависимость и объявить бин
DataSource:
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.DERBY)
.build();
}
EmbeddedDatabaseType это enum, в котором есть H2, HSQL и DERBY. Еще можно указать параметр generateUniqueName и при каждом создании бина у базы данных будет уникальное имя, а если указать addScript, то можно добавить имя файла из ресурсов, в котором будет SQL-скрипт для создания структуры таблиц или добавления данных. Зачем может понадобиться отдельный
DataSource с in-memory базой данных? Для тестов, например. А если хотите добавить поддержку свой базы, то можно через setDataSourceFactory передать свой DataSourceFactory, который будет, например, в докере запускать CouchDB.❤11🔥7👍4❤🔥1🥰1💘1
Spring Shorts №2 - SQL Query
Расскажу еще про одну малоизвестную возможность Spring JDBC (не того, который Spring Data JDBC, а обычного). Самым часто используемым компонентом из этого модуля по праву считается
В Spring Data есть отличная фича - при запуске приложения валидирует, правильно ли названы методы и есть ли в базе нужные колонки в таблицах. Hibernate делает то же самое для HQL, а вот у
Сделаю небольшой сетап - у меня будет таблица
Доменный объект
А теперь, запрос на получение всех людей из базы данных:
Все самое интересное - в конструкторе. Компонент зависит от
Вторая часть - маппер в методе
Но как быть, когда в запросе есть параметры? Очень просто - параметры можно объявить и убедиться, что в запросе они точно есть. Выглядит вот так:
Так как это отдельный класс, то можно добавить дополнительные методы, например,
Расскажу еще про одну малоизвестную возможность Spring JDBC (не того, который Spring Data JDBC, а обычного). Самым часто используемым компонентом из этого модуля по праву считается
JdbcTemplate - он и в базу пишет, и читает, и запросы выполняет. В Spring Data есть отличная фича - при запуске приложения валидирует, правильно ли названы методы и есть ли в базе нужные колонки в таблицах. Hibernate делает то же самое для HQL, а вот у
JdbcTemplate такой возможности нет. Зато в Spring JDBC есть класс SqlQuery, который позволяет обернуть обычный SQL запрос в объект и также валидировать при старте приложения. Сделаю небольшой сетап - у меня будет таблица
people, которая создается следующим скриптом:
CREATE TABLE people (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
Доменный объект
Person:
public record Person(
Integer id,
String name,
Instant createdAt) {
public Person(String name) {
this(null, name, Instant.now());
}
public Person withId(int id) {
return new Person(id, name, createdAt);
}
}
А теперь, запрос на получение всех людей из базы данных:
public class FindPeopleQuery extends MappingSqlQuery<Person> {
public FindPeopleQuery(DataSource dataSource) {
super(
dataSource,
"SELECT id, name, created_at FROM people"
);
compileInternal();
}
@Override
protected Person mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Person(
rs.getInt("id"),
rs.getString("name"),
rs.getTimestamp("created_at").toInstant()
);
}
}
Все самое интересное - в конструкторе. Компонент зависит от
DataSource, поэтому мы передаем его в качестве параметра в конструктор, а дальше вызываем родительский конструктор и передаем в него запрос, а затем вызываем compileInternal. compileInternal создает PreparedStatement и в этот-то момент и происходит валидация.
public FindPeopleQuery(DataSource dataSource) {
super(
dataSource,
"SELECT id, name, created_at FROM people"
);
compileInternal();
}
Вторая часть - маппер в методе
mapRow, который конвертирует строку из базы данных в доменный объект. Ну, здесь обычный RowMapper, такой же как и в JdbcTemplate. Но как быть, когда в запросе есть параметры? Очень просто - параметры можно объявить и убедиться, что в запросе они точно есть. Выглядит вот так:
public class FindPersonByIdQuery extends MappingSqlQuery<Person> {
public FindPersonByIdQuery(DataSource dataSource) {
super(
dataSource,
"SELECT id, name, created_at FROM people WHERE id = ?"
);
declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}
public List<Person> findAll() {
return execute();
}
}
Так как это отдельный класс, то можно добавить дополнительные методы, например,
findAll и из него уже вызывать внутренние методы SqlQuery.🔥11
Spring Shorts №3 - Simple JDBC Insert
В прошлый раз мы посмотрели на
Воспользуемся таблицей из прошлого поста:
Вставлять будем
В конструкторе создаем
И самое главное -
а еще можно завернуть доменный объект в
А вы что используете для доступа к базе данных? Вы все еще кипятите Hibernate? Тогда мы идем к вам!
В прошлый раз мы посмотрели на
SqlQuery, но есть ведь еще SimpleJdbcInsert, который позволяет избежать написания запросов на вставку. Почему именно их? Потому что запросы на вставку довольно большие - insert into table values (column1, column2) values (:value1, :value2), а еще важно не перепутать порядок параметров и колонок. Воспользуемся таблицей из прошлого поста:
CREATE TABLE people (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
Вставлять будем
Person - такой же, как и до этого. А для вставки создадим класс InsertPersonQuery:
public class InsertPersonQuery {
private final SimpleJdbcInsert jdbcInsert;
public InsertPersonQuery(DataSource dataSource) {
this.jdbcInsert = new SimpleJdbcInsert(dataSource)
.withTableName("people")
.usingGeneratedKeyColumns("id")
.usingColumns("name", "created_at");
}
public Person insertPerson(Person person) {
final int generatedId = jdbcInsert.execute(Map.of(
"name", person.name(),
"created_at", person.createdAt()
));
return person.withId(generatedId);
}
}
В конструкторе создаем
SimpleJdbcInsert, указываем, в какую таблицу вставлять и значения каких колонок генерируются автоматически базой данных. Также можно указать имена колонок для вставки. И самое главное -
jdbcInsert.execute. Сюда можно передать Map с параметрами, выглядит вот так:
final int generatedId = jdbcInsert.execute(Map.of(
"name", person.name(),
"created_at", person.createdAt()
));
а еще можно завернуть доменный объект в
BeanPropertySqlParameterSource и тогда Spring будет сам перекладывать значения из объекта в запрос:
public Person insertPerson(Person person) {
final int generatedId = jdbcInsert
.execute(new BeanPropertySqlParameterSource(person));
return person.withId(generatedId);
}
А вы что используете для доступа к базе данных? Вы все еще кипятите Hibernate? Тогда мы идем к вам!
🔥9👍5
Mockito Shorts №1 - Введение
В прошлый раз был Spring, до этого был Spring и вообще почти всегда я пишу и рассказываю про Spring. Пора рассказать о чем-то другом - сегодня будет про Mockito. Да, про библиотеку для замены реальных зависимостей на настраиваемые тестовые. Рассказывать я буду про совместную работу с JUnit 5 и MockitoExtension.
Юнит-тест с Mockito обычно выглядит как-то так:
Чем занимается
1. Перед запуском каждого теста он вызывает
2. После выполнения каждого теста вызывает
3. А еще
Можно обойтись и без
Чтобы создать мок для
Или же создать мок вручную:
Теперь у нас есть мок и для работы его нужно настроить. Сделать это можно через метод
Что будет, если я попытаюсь вызвать
У Mockito достаточно много параметров, которые могут упростить работу и первый из них это smart nullls:
Или
В этом случае сообщение об ошибке будет более развернутое:
Если хочется замокать
В прошлый раз был Spring, до этого был Spring и вообще почти всегда я пишу и рассказываю про Spring. Пора рассказать о чем-то другом - сегодня будет про Mockito. Да, про библиотеку для замены реальных зависимостей на настраиваемые тестовые. Рассказывать я буду про совместную работу с JUnit 5 и MockitoExtension.
Юнит-тест с Mockito обычно выглядит как-то так:
@ExtendWith(MockitoExtension.class)
public class MockitoExtensionDemoTest {
}
Чем занимается
MockitoExtension? Он выполняет несколько действий:1. Перед запуском каждого теста он вызывает
Mockito.mockitoSession().initMocks(this), который для всех свойств с аннотацией @Mock создает и настраивает объект мока, для всех свойств с аннотацией @Spy создает частичный мок (экземпляр класса, обернутый в мок), а также для всех свойств с аннотацией @InjectMocks создает экземпляр и внедряет зависимости. 2. После выполнения каждого теста вызывает
Mockito.validateMockitoUsage(). Если вы видели сообщения вида Unnecessary stubbing или MissingMethodInvocationException, то это результат работы validateMockitoUsage - этот метод проверяет, что все используемые моки правильно настроены и использованы. 3. А еще
MockitoExtension проверяет на наличие на тестовом классе аннотации @MockitoSettings, которая позволяет настроить строгость и не показывать ошибок вида unnecessary stubbing. Можно обойтись и без
MockitoExtension, если создавать моки самостоятельно. Для этого вызываем Mockito.mock() и передаем в качестве параметра класс, для которого нужно создать мок. Например, у меня есть следующие связанные компоненты, которые я хочу использовать в своем тесте:
interface ExecutionContext {
ContextQuestions getQuestions();
}
interface ContextQuestions {
boolean isFeatureEnabled(String featureName);
}
Чтобы создать мок для
ExecutionContext можно создать поле в тесте и поставить на него аннотацию @Mock:
@ExtendWith(MockitoExtension.class)
public class MockitoExtensionDemo2Test {
@Mock
ExecutionContext context;
}
Или же создать мок вручную:
public class MockitoExtensionDemo2Test {
ExecutionContext context;
@BeforeEach
void setUp() {
context = mock(ExecutionContext.class);
}
}
Теперь у нас есть мок и для работы его нужно настроить. Сделать это можно через метод
when:
@Test
void getQuestions_shouldReturnQuestions() {
final ContextQuestions questions = featureName -> true;
when(context.getQuestions()).thenReturn(questions);
assertThat(context.getQuestions()).isNotNull();
}
Что будет, если я попытаюсь вызвать
context.getQuestions().isFeatureEnabled() без вызова when? Очевидно, будет NPE:
@Test
void getQuestions_shouldThrowException() {
assertThatThrownBy(() -> context.getQuestions().isFeatureEnabled("abc"))
.isInstanceOf(NullPointerException.class);
}
У Mockito достаточно много параметров, которые могут упростить работу и первый из них это smart nullls:
@Test
void getQuestions_withSmartNulls() {
context = mock(ExecutionContext.class, Answers.RETURNS_SMART_NULLS);
context.getQuestions().isFeatureEnabled("abc");
}
Или
@Mock(answer = Answers.RETURNS_SMART_NULLS)
ExecutionContext context;
В этом случае сообщение об ошибке будет более развернутое:
You have a NullPointerException here:
… name of the class goes here
because this method call was *not* stubbed correctly:
Если хочется замокать
ContextQuestions, который находится внутри ExecutionContext, то можно создать два мока:
@Test
void getQuestions_nestedMocks() {
ContextQuestions questions = mock(ContextQuestions.class);
when(context.getQuestions()).thenReturn(questions);
when(questions.isFeatureEnabled("abc")).thenReturn(true);
assertThat(context.getQuestions().isFeatureEnabled("abc")).isTrue();
}
🔥5
Обычно так и делается, однако, Mockito может существенно облегчить жизнь, если использовать
Кроме этого, можно сразу настраивать возвращаемые моки:
Ну, и напоследок, можно попросить Mockito вызывать реальные методы вместо моков:
Это все на сегодня, в следующий раз посмотрим, как можно настроить мок с помощью
Anwers.RETURN_DEEP_STUBS. В этом случае код больше не бросает NPE, а возвращает мок, который возвращает значение по умолчанию:
@Test
void getQuestions_withDeepMocks() {
assertThat(context.getQuestions().isFeatureEnabled("abc")).isFalse();
}
Кроме этого, можно сразу настраивать возвращаемые моки:
@Test
void getQuestions_nestedMocks() {
when(context.getQuestions().isFeatureEnabled("abc")).thenReturn(true);
assertThat(context.getQuestions().isFeatureEnabled("abc")).isTrue();
}
Ну, и напоследок, можно попросить Mockito вызывать реальные методы вместо моков:
@ExtendWith(MockitoExtension.class)
class WithRealMethods {
@Mock(answer = Answers.CALLS_REAL_METHODS)
AllEnabledQuestions questions;
@InjectMocks
DefaultExecutionContext context;
@Test
void getQuestions_willCallRealMethods() {
assertThat(context.getQuestions().isFeatureEnabled("abc")).isTrue();
}
}
Это все на сегодня, в следующий раз посмотрим, как можно настроить мок с помощью
thenAnswer вместо thenReturn.👍8🔥4
Продолжим говорить о Mockito. В прошлом посте я рассказал о том, как настроить мок, чтобы он возвращал заранее заданное значение, сегодня расскажу о том, как сделать так, чтобы мок вычислял значение на лету. Для этого нужно использовать
В данном примере мы можем получить доступ к аргументам метода через параметр
Согласитесь, каждый раз писать
В качестве параметра метод
Отдельно нужно сказать, что если метод должен выбрасывать исключение вместо возвращения результата, то можно использовать
В случае, если метод мока ничего не возвращает, то стандартную конструкцию
Спасибо, что читаете мои посты, в заключительном посте расскажу о том, как проверять, с какими параметрами вызывались методы мока.
thenAnswer.
@ExtendWith(MockitoExtension.class)
class WithAnswer {
@Mock
ContextQuestions questions;
@Test
void isFeatureEnabled_withAnswer() {
when(questions.isFeatureEnabled(anyString())).thenAnswer(inv -> {
final String name = inv.getArgument(0, String.class);
return "abc".equals(name) || "xyz".equals(name);
});
assertThat(questions.isFeatureEnabled("abc")).isTrue();
}
}
В данном примере мы можем получить доступ к аргументам метода через параметр
invocation и далее через getArgument. Согласитесь, каждый раз писать
inv.getArgument может быть утомительно, особенно для методов с одним параметром. Чтобы этого избежать можно использовать AdditionalAnswers:
@Test
void isFeatureEnabled_withAnswers() {
when(questions.isFeatureEnabled(anyString())).then(answer((Answer1<Boolean, String>) arg0 ->
"abc".equals(arg0) || “xyz".equals(arg0)));
assertThat(questions.isFeatureEnabled("abc")).isTrue();
}
В качестве параметра метод
answer принимает типизированный интерфейс AnswerX или AnswerVoid, в котором в качестве типов указываются тип возвращаемого значения и тип параметров метода. Таким образом избавились от постоянных вызовов inv.getArgument. Отдельно нужно сказать, что если метод должен выбрасывать исключение вместо возвращения результата, то можно использовать
thenThrow.
@Test
void isFeatureEnabled_withException() {
when(questions.isFeatureEnabled(anyString())).thenThrow(new IllegalStateException("Not implemented"));
assertThatThrownBy(() -> questions.isFeatureEnabled("abc"))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Not implemented");
}
В случае, если метод мока ничего не возвращает, то стандартную конструкцию
when(mock).thenReturn() использовать не получится. Вместо этого нужно использовать конструкции doAnswer, doReturn и doThrow:
@ExtendWith(MockitoExtension.class)
class WithVoidReturnType {
@Mock
DefaultExecutionContext context;
@Test
void doCheck_withAnswer() {
doAnswer(invocation -> {
// Simulate some logic
return null;
}).when(context).doCheck();
context.doCheck();
verify(context).doCheck();
}
}
Спасибо, что читаете мои посты, в заключительном посте расскажу о том, как проверять, с какими параметрами вызывались методы мока.
👍11🔥3❤1
Продолжим говорить о Mockito. Мы уже научились управлять поведением моков, теперь пора поговорить о проверках — с какими параметрами и в каком порядке вызывались методы. В этом посте расскажу о самых полезных инструментах:
✅ verify — как проверить вызов метода
Если вы хотите убедиться, что метод был вызван, используйте verify. Это самый частый способ проверок в Mockito.
Можно также проверять, сколько раз вызывался метод:
Mockito также позволяет проверить и асинхронные вызов - взаимодействие с моком произошло в течение указанного промежутка времени:
🚫 verifyNoInteractions — метод не должен вызываться
Иногда нужно убедиться, что с мок-объектом вообще не взаимодействовали:
А если вы хотите проверить, что после каких-то действий не было дополнительных вызовов, используйте
🔁 InOrder — проверка порядка вызовов
Бывает важно, в каком порядке вызывались методы — тогда пригодится InOrder.
Можно передать в
🎯 @Captor — захват аргументов
Если вы хотите получить значение аргумента, с которым вызывался метод — используйте
Не забудьте аннотацию
Кстати, можно создать
🧪 assertArg — лаконичная проверка аргумента
Начиная с Mockito 5, можно использовать assertArg короткий способ проверить аргумент прямо в verify без использования `ArgumentCaptor`-ов.
Пишите в комментариях, о каких еще интересных возможностях Mockito я не рассказал. А дальше расскажу об одной из моих любимых библиотек для тестирования - AssertJ.
verify, verifyNoInteractions, InOrder, @Captor и assertArg.✅ verify — как проверить вызов метода
Если вы хотите убедиться, что метод был вызван, используйте verify. Это самый частый способ проверок в Mockito.
@Test
void verify_example() {
questions.isFeatureEnabled("abc");
verify(questions).isFeatureEnabled("abc");
}
Можно также проверять, сколько раз вызывался метод:
verify(questions, times(1)).isFeatureEnabled("abc"); // по умолчанию 1
verify(questions, never()).isFeatureEnabled(“xyz”);
verify(questions, atLeastOnce()).isFeatureEnabled(“xyz”);
verify(questions, atLeast(2)).isFeatureEnabled(“xyz");
Mockito также позволяет проверить и асинхронные вызов - взаимодействие с моком произошло в течение указанного промежутка времени:
verify(context, timeout(1000)).doCheck(); // ждать до одной секунды, что произойдет взаимодействие
🚫 verifyNoInteractions — метод не должен вызываться
Иногда нужно убедиться, что с мок-объектом вообще не взаимодействовали:
@Test
void noInteractions_example() {
verifyNoInteractions(questions); // важно: вызовов не было вообще
}
А если вы хотите проверить, что после каких-то действий не было дополнительных вызовов, используйте
verifyNoMoreInteractions:
questions.isFeatureEnabled("abc");
verify(questions).isFeatureEnabled("abc");
verifyNoMoreInteractions(questions); // ни одного вызова больше
🔁 InOrder — проверка порядка вызовов
Бывает важно, в каком порядке вызывались методы — тогда пригодится InOrder.
@Test
void inOrder_example() {
questions.isFeatureEnabled("abc");
questions.isFeatureEnabled("xyz");
InOrder inOrder = inOrder(questions);
inOrder.verify(questions).isFeatureEnabled("abc");
inOrder.verify(questions).isFeatureEnabled("xyz");
}
Можно передать в
inOrder(...) несколько моков и проверять, как они взаимодействовали между собой.🎯 @Captor — захват аргументов
Если вы хотите получить значение аргумента, с которым вызывался метод — используйте
@Captor. Особенно удобно, если метод принимал сложный объект.
@Captor
ArgumentCaptor<String> stringCaptor;
@Test
void captor_example(@Mock ContextQuestions questions) {
questions.isFeatureEnabled("abc");
verify(questions).isFeatureEnabled(stringCaptor.capture());
assertThat(stringCaptor.getValue()).isEqualTo("abc");
}
Не забудьте аннотацию
@ExtendWith(MockitoExtension.class) в классе, чтобы @Captor работал.Кстати, можно создать
ArgumentCaptor прямо в самом тесте и без @Captor:
@Test
void captor_example(@Mock ContextQuestions questions) {
questions.isFeatureEnabled("abc");
final ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
verify(questions).isFeatureEnabled(stringCaptor.capture());
assertThat(stringCaptor.getValue()).isEqualTo("abc");
}
🧪 assertArg — лаконичная проверка аргумента
Начиная с Mockito 5, можно использовать assertArg короткий способ проверить аргумент прямо в verify без использования `ArgumentCaptor`-ов.
@Test
void assertArg_example() {
questions.isFeatureEnabled("abc");
verify(questions).isFeatureEnabled(assertArg(arg -> {
assertThat(arg).startsWith("a");
}));
}
Пишите в комментариях, о каких еще интересных возможностях Mockito я не рассказал. А дальше расскажу об одной из моих любимых библиотек для тестирования - AssertJ.
👍6🔥2
AssertJ Shorts №1
Как и обещал, пост про мою любимую библиотеку для тестов - AssertJ. У проекта есть официальный веб-сайт https://assertj.github.io/doc/, он большой, красивый и там есть масса примеров, как круто можно использовать AssertJ.
Вообще, в JUnit есть и своя библиотека assertion-ов, которая неплохо расширяется, например, Hamcrest-ом. Из плюсов - это просто и доступно из коробки, из минусов - недостаточно выразительно. Что доступно в JUnit:
В билиотеке JUnit есть еще несколько assert-ов, но все они вида assertEqual/assertNotEqual/assertTrue. В AssertJ выбор гораздо больше и все начинается с метода assertThat:
И дальше уже можно по цепочке добавлять проверки. В зависимости от типа параметра доступны различные вспомогательные методы, например, для целых чисел проверки уже другие:
Есть удобная поддержка Optional-ов:
Проверка сложных объектов с AssertJ - это песня! Например, нужно сделать несколько проверок сразу и только если все проходят, только тогда объект удовлетворяет условиям:
Работа с исключениями в AssertJ - просто песня:
А какую библиотеку используете вы в своих проектах?
Как и обещал, пост про мою любимую библиотеку для тестов - AssertJ. У проекта есть официальный веб-сайт https://assertj.github.io/doc/, он большой, красивый и там есть масса примеров, как круто можно использовать AssertJ.
Вообще, в JUnit есть и своя библиотека assertion-ов, которая неплохо расширяется, например, Hamcrest-ом. Из плюсов - это просто и доступно из коробки, из минусов - недостаточно выразительно. Что доступно в JUnit:
String name = "assertJ”;
assertEquals(“assertJ”, name);
В билиотеке JUnit есть еще несколько assert-ов, но все они вида assertEqual/assertNotEqual/assertTrue. В AssertJ выбор гораздо больше и все начинается с метода assertThat:
import static org.assertj.core.api.Assertions.*;
@Test
void basic_assertions() {
String name = "assertj";
assertThat(name)
.startsWith("asser”)
.endsWith("tj")
.isEqualToIgnoringCase("ASSERTJ")
.doesNotContain("mockito");
}
И дальше уже можно по цепочке добавлять проверки. В зависимости от типа параметра доступны различные вспомогательные методы, например, для целых чисел проверки уже другие:
@Test
void number_example() {
assertThat(42)
.isGreaterThan(10)
.isLessThanOrEqualTo(100)
.isNotZero();
}
Есть удобная поддержка Optional-ов:
@Test
void optional_example() {
Optional<String> value = Optional.of("hello");
assertThat(value)
.isPresent()
.contains("hello");
}
@Test
void optional_empty() {
Optional<String> value = Optional.empty();
assertThat(value)
.isEmpty();
}
@Test
void optional_value() {
Object content = new Object();
Optional<Object> value = Optional.of(content);
assertThat(value)
.hasValue(content);
}
Проверка сложных объектов с AssertJ - это песня! Например, нужно сделать несколько проверок сразу и только если все проходят, только тогда объект удовлетворяет условиям:
@Test
void custom_check() {
User user = new User("Alex", 30);
assertThat(user).satisfies(u -> {
assertThat(u.name).startsWith("A");
assertThat(u.age).isBetween(18, 60);
});
}
Работа с исключениями в AssertJ - просто песня:
@Test
void exception_example() {
assertThatThrownBy(() -> {
throw new IllegalArgumentException("bad input");
})
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("bad");
}
А какую библиотеку используете вы в своих проектах?
🔥11👍2❤1
AssertJ Shorts №2 - Collections
Продолжим про AssertJ. Уверен, за неделю вы уже смогли попробовать библиотеку в деле, поэтому расскажу о других интересных возможностях, которые не бросаются в глаза при чтении документации.
Начнем с моей любимой фичи - удобной работе с коллекциями.
Если у вас есть список объектов и вы хотите проверить их свойства — не надо писать stream().map(...). Просто extracting.
Можно вытаскивать сразу несколько полей:
И даже выполнять преобразование на извлеченных значениях:
В моей практике часто нужно проверять и коллекцию из единственного элемента. Здесь у AssertJ есть несколько полезных методов, например, если в коллекции должен быть только один элемент, то к нему можно напрямую обратиться через singleElement:
Если элемент нужно проверить на соблюдение нескольких условий, то можно использовать singleElementSatisfies:
Как быть, если в коллекции есть несколько разношерстных элементов, причем все должны удовлетворять определенному условию, несколько - отдельному условию, и ни один - третьем. И здесь у AssertJ есть отличный набор вспомогательных методов - allMatch, anyMatch и noneMatch:
Ну и напоследок еще несколько полезных методов по работе с коллекциями. Например, проверка размера коллекции, содержимого и порядка элементов в ней:
Коллекцию перед проверкой можно и отфильтровать (и по значению поля тоже):
В заключительной части расскажу о том, как сделать свой собственный custom assert.
Продолжим про AssertJ. Уверен, за неделю вы уже смогли попробовать библиотеку в деле, поэтому расскажу о других интересных возможностях, которые не бросаются в глаза при чтении документации.
Начнем с моей любимой фичи - удобной работе с коллекциями.
Если у вас есть список объектов и вы хотите проверить их свойства — не надо писать stream().map(...). Просто extracting.
record User(String name, int age) {}
@Test
void extracting_example() {
List<User> users = List.of(
new User("Anna", 25),
new User("Bob", 30)
);
assertThat(users)
.extracting(User::name)
.containsExactly("Anna", "Bob");
}
Можно вытаскивать сразу несколько полей:
assertThat(users)
.extracting("name", "age")
.containsExactly(
tuple("Anna", 25),
tuple("Bob", 30)
);
И даже выполнять преобразование на извлеченных значениях:
assertThat(users)
.extracting(u -> u.name.toUpperCase())
.contains("ANNA", "BOB");
В моей практике часто нужно проверять и коллекцию из единственного элемента. Здесь у AssertJ есть несколько полезных методов, например, если в коллекции должен быть только один элемент, то к нему можно напрямую обратиться через singleElement:
@Test
void single_element_example() {
List<String> names = List.of("Alice");
assertThat(names)
.singleElement()
.isEqualTo("Alice");
}
Если элемент нужно проверить на соблюдение нескольких условий, то можно использовать singleElementSatisfies:
@Test
void single_element_satisfies_example() {
List<User> users = List.of(new User("Ann", 21));
assertThat(users).singleElementSatisfies(user -> {
assertThat(user.name).startsWith("A");
assertThat(user.age).isGreaterThan(18);
});
}
Как быть, если в коллекции есть несколько разношерстных элементов, причем все должны удовлетворять определенному условию, несколько - отдельному условию, и ни один - третьем. И здесь у AssertJ есть отличный набор вспомогательных методов - allMatch, anyMatch и noneMatch:
@Test
void any_match_example() {
List<String> names = List.of("Anna", "Bob", "Charlie");
assertThat(names)
.anyMatch(name -> name.length() == 3); // Bob
}
@Test
void all_match_example() {
List<String> names = List.of("Anna", "Alan");
assertThat(names)
.allMatch(name -> name.startsWith("A"));
}
@Test
void none_match_example() {
List<String> names = List.of("Anna", "Bob", "Charlie");
assertThat(names)
.noneMatch(name -> name.length() > 10); // никто не слишком длинный
}
Ну и напоследок еще несколько полезных методов по работе с коллекциями. Например, проверка размера коллекции, содержимого и порядка элементов в ней:
List<String> names = List.of("Anna", "Bob", "Charlie");
assertThat(names)
.hasSize(3)
.contains("Bob", "Anna")
.containsExactly("Anna", "Bob", "Charlie") // и порядок важен!
.doesNotContain("Diana");
Коллекцию перед проверкой можно и отфильтровать (и по значению поля тоже):
List<String> names = List.of("Anna", "Bob", "Alice");
assertThat(names)
.filteredOn(name -> name.startsWith("A"))
.containsExactly("Anna", "Alice");
В заключительной части расскажу о том, как сделать свой собственный custom assert.
🔥10👍4
AssertJ Shorts №3 - Custom Assertion
Кастомные assertions — одна из сильных сторон AssertJ. Они позволяют писать тесты, которые выглядят как читаемый DSL, например:
Разберем по шагам, как сделать собственный assertion. Для начала смоделируем класс, который будем в дальнейшем тестировать:
А теперь приступим как самому assertion - создаем класс, наследуем его от
Здесь (1) - конструктор, который принимает проверяемый объект, а (2) - первый вызов
И в тестах теперь можно написать:
Кроме собственных assertion-ов можно еще создать condition, который используется для проверки элементов коллекции. Подойдет, если одно и то же условие используется во многих местах:
А,
Если условие используется только один раз, то можно использовать match:
О каких еще возможностях библиотеки assertJ вы хотели бы рассказать?
Кастомные assertions — одна из сильных сторон AssertJ. Они позволяют писать тесты, которые выглядят как читаемый DSL, например:
assertThat(user).hasValidEmail().isAdult();
Разберем по шагам, как сделать собственный assertion. Для начала смоделируем класс, который будем в дальнейшем тестировать:
@Value
public class User {
private final String name;
private final String email;
private final int age;
}
А теперь приступим как самому assertion - создаем класс, наследуем его от
AbstractAssert, а в качестве параметров типов указываем класс assertion-а и тот класс, который assertion должен проверять:
import org.assertj.core.api.AbstractAssert;
public class UserAssert extends AbstractAssert<UserAssert, User> {
// (1)
public UserAssert(User actual) {
super(actual, UserAssert.class);
}
// (2)
public static UserAssert assertThat(User actual) {
return new UserAssert(actual);
}
}
Здесь (1) - конструктор, который принимает проверяемый объект, а (2) - первый вызов
assertThat, который создаст экземпляр assertion-а. Теперь остается только добавить методы для проверки:
import org.assertj.core.api.AbstractAssert;
public class UserAssert extends AbstractAssert<UserAssert, User> {
// конструктор и assertThat должны быть здесь
public UserAssert hasValidEmail() {
isNotNull();
if (actual.getEmail() == null || !actual.getEmail().contains("@")) {
failWithMessage("Expected user to have valid email, but was <%s>", actual.getEmail());
}
return this;
}
public UserAssert isAdult() {
isNotNull();
if (actual.getAge() < 18) {
failWithMessage("Expected user to be adult (18+), but was <%s>", actual.getAge());
}
return this;
}
}
И в тестах теперь можно написать:
import static your.package.UserAssert.assertThat;
@Test
void custom_assertion_example() {
User user = new User("Alex", "alex@example.com", 20);
assertThat(user)
.hasValidEmail()
.isAdult();
}
Кроме собственных assertion-ов можно еще создать condition, который используется для проверки элементов коллекции. Подойдет, если одно и то же условие используется во многих местах:
@Test
void condition_example() {
User user = new User("Alice", "alice@example.com", 20);
assertThat(user).has(isAdult);
List<User> users = List.of(
new User("Bob", "bob@texample.com", 17),
new User("Alice", "alice@example.com", 20)
);
assertThat(users).haveExactly(1, isAdult); // один взрослый
}
А,
isAdult в этом случае - отдельный класс:
import org.assertj.core.api.Condition;
Condition<User> isAdult = new Condition<>(
user -> user.age() >= 18,
"an adult (18+)"
);
Если условие используется только один раз, то можно использовать match:
assertThat(user)
.matches(u -> u.age() >= 18, "age is 18+");
О каких еще возможностях библиотеки assertJ вы хотели бы рассказать?
🔥5👍4
AssertJ Shorts №4 - Recursive Comparison и Factories
Как верно заметили в комментариях, я не рассказал еще про некоторые важные фишки AssertJ, такие как
Иногда обычный
Он позволяет сравнивать объекты рекурсивно, т.е. по всем вложенным полям, даже если это разные экземпляры одного класса.
Без
Кроме рекурсивного сравнения,
Еще более интересная возможность - сравнивать списки объектов через
Некоторые поля нужно сравнивать по особым правилам, например,
Здесь
Если одни и те же настройки сравнения используются несколько раз, их можно вытащить в отдельную конфигурацию и использовать несколько раз:
И кратко про
Чтобы сохранить тип и использовать нужные ассёрты, можно использовать
А теперь с
Как верно заметили в комментариях, я не рассказал еще про некоторые важные фишки AssertJ, такие как
usingRecursiveComparison и InstanceOfAssertFactory. Спасибо, что заметили, исправляюсь!Иногда обычный
assertThat(actual).isEqualTo(expected) не работает — например, если сравниваемые объекты содержат вложенные поля, коллекции или даже циклические ссылки. В таких случаях на помощь приходит usingRecursiveComparison().Он позволяет сравнивать объекты рекурсивно, т.е. по всем вложенным полям, даже если это разные экземпляры одного класса.
record Address(String city, String zip) {}
record User(String name, Address address) {}
@Test
void recursive_comparison_simple() {
User actual = new User("Alice", new Address("London", "12345"));
User expected = new User("Alice", new Address("London", "12345"));
assertThat(actual)
.usingRecursiveComparison()
.isEqualTo(expected);
}
Без
usingRecursiveComparsion тест бы упал, так как объекты Address разные. Кроме рекурсивного сравнения,
usingRecursiveComparison позволяет исключать некоторые поля из сравнения. Особенно полезно, например, для полей типа createdAt и updatedAt, значения которых точно не совпадают у разных объектов:
record User(String name, int age) {}
@Test
void ignore_field() {
User actual = new User("Alice", 30);
User expected = new User("Alice", 99); // возраст не важен
assertThat(actual)
.usingRecursiveComparison()
.ignoringFields("age")
.isEqualTo(expected);
}
Еще более интересная возможность - сравнивать списки объектов через
usingRecursiveFieldByFieldElementComparator:
List<User> actual = List.of(new User("Alice", new Address("London", "12345")));
List<User> expected = List.of(new User("Alice", new Address("London", "12345")));
assertThat(actual)
.usingRecursiveFieldByFieldElementComparator()
.containsExactlyElementsOf(expected);
Некоторые поля нужно сравнивать по особым правилам, например,
BigDecimal с учетом или без учета точности. Здесь помогут withComparatorForFields и withEqualsForType:
record Price(BigDecimal amount) {}
@Test
void with_comparator_for_field() {
Price actual = new Price(new BigDecimal("10.005"));
Price expected = new Price(new BigDecimal("10.004"));
Comparator<BigDecimal> roundedComparator =
Comparator.comparing(bd -> bd.setScale(2, RoundingMode.HALF_UP));
assertThat(actual)
.usingRecursiveComparison()
.withComparatorForFields(roundedComparator, "amount")
.isEqualTo(expected);
}
Здесь
amount сравнивается уже после округления до двух знаков после запятой. Если одни и те же настройки сравнения используются несколько раз, их можно вытащить в отдельную конфигурацию и использовать несколько раз:
RecursiveComparisonConfiguration config = RecursiveComparisonConfiguration.builder()
.withIgnoredFields("id", "timestamp")
.withIgnoreAllActualNullFields(true)
.build();
assertThat(actual)
.usingRecursiveComparison(config)
.isEqualTo(expected);
И кратко про
InstanceOfAssertFactory - совсем забыл, насколько крутая это фича. В обычных ассёртах мы можем легко проверять свойства объектов, но при использовании методов вроде extracting() или filteredOn() тип выводится как Object, и AssertJ не знает, какие методы доступны у результата.Чтобы сохранить тип и использовать нужные ассёрты, можно использовать
InstanceOfAssertFactory. Без InstanceOfAssertFactory:
List<Person> people = List.of(new Employee("Alice", "HR"), new Manager("Bob", 5));
assertThat(people)
.filteredOn(p -> p instanceof Manager)
.first() // тип Object
.extracting("teamSize") // работает, но не типобезопасно
.isEqualTo(5);
А теперь с
InstanceOfAssertFactory:❤2