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
InstanceOfAssertFactory<Manager, ObjectAssert<Manager>> managerAssert =
new InstanceOfAssertFactory<>(Manager.class, Assertions::assertThat);
assertThat(people)
.filteredOn(p -> p instanceof Manager)
.first(managerAssert) // теперь тип — Manager
.extracting(Manager::getTeamSize)
.isEqualTo(5);
Если ты часто используешь типы вроде String, Integer, Map, List, LocalDateTime и т.д., то нет необходимости создавать InstanceOfAssertFactory вручную — AssertJ уже поставляет готовые!
import static org.assertj.core.api.InstanceOfAssertFactories.*;
@Test
void use_predefined_instance_of_assert_factory() {
record Event(String type, Map<String, Object> payload) {}
var events = List.of(
new Event("click", Map.of("x", 10, "y", 20)),
new Event("submit", Map.of("formId", "abc123"))
);
assertThat(events)
.element(0)
.extracting(Event::payload, MAP) // используем готовую фабрику
.containsEntry("x", 10);
}
Спасибо большое за комментарии, пишите еще!
🔥10👍3
Awaility Shorts
Мне кажется, про AssertJ я рассказал все, что знал и использовал сам каждый день. Перейдем к следующей библиотеке, которая мне также помогает каждый день - Awaitility. Если есть необходимость проверить результаты работы, которая происходит в фоне, то Awaitility здесь просто незаменима. Естественно, всегда есть возможность сделать
Например, здесь тест ждет до трех секунд, пока значение
Условие можно передать и лямбдой:
DSL Awaitility включает еще несколько очень полезных методов, например,
Иногда проверяемый метод может бросать исключения до момента полной инициализации. Эти исключения можно игнорировать через
Также можно игнорировать исключения только определенных типов:
И что я люблю больше всего, так это интеграцию с AssertJ через
Используете ли вы библиотеку Awaility в своей работе?
Мне кажется, про AssertJ я рассказал все, что знал и использовал сам каждый день. Перейдем к следующей библиотеке, которая мне также помогает каждый день - Awaitility. Если есть необходимость проверить результаты работы, которая происходит в фоне, то Awaitility здесь просто незаменима. Естественно, всегда есть возможность сделать
Thread.sleep(1000) в цикле, но Awaitility предлагает гораздо более удобный DSL. Например, здесь тест ждет до трех секунд, пока значение
flag сменится на true. Если за три секунду этого не произойдет - тест упадет.
import static org.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
@Test
void shouldWaitUntilValueIsSet() {
AtomicBoolean flag = new AtomicBoolean(false);
Executors.newSingleThreadScheduledExecutor()
.schedule(() -> flag.set(true), 1, SECONDS);
await().atMost(3, SECONDS)
.untilTrue(flag);
}
Условие можно передать и лямбдой:
await().atMost(2, SECONDS)
.until(() -> repository.findAll().size() == 3);
DSL Awaitility включает еще несколько очень полезных методов, например,
pollInterval - как часто проверять:
await()
.pollInterval(200, MILLISECONDS)
.atMost(5, SECONDS)
.until(() -> repo.isInitialized());
atLeast - минимальное время ожидания, даже если условие будет выполнено сразу, то тест все равно подождет указанное в atLeast время:
await()
.atLeast(1, SECONDS) // в любом случае ждем одну секунду
.atMost(5, SECONDS)
.until(() -> cache.isWarm());
Иногда проверяемый метод может бросать исключения до момента полной инициализации. Эти исключения можно игнорировать через
ignoreException:
await()
.ignoreExceptions()
.atMost(2, SECONDS)
.until(() -> service.getStatus().equals("READY"));
Также можно игнорировать исключения только определенных типов:
await()
.ignoreException(IllegalStateException.class)
.until(() -> client.getConnection().isAlive());
И что я люблю больше всего, так это интеграцию с AssertJ через
untilAsserted:
await()
.atMost(3, SECONDS)
.untilAsserted(() -> {
assertThat(store.getData()).isNotEmpty();
assertThat(store.getData()).containsExactly("hello");
});
Используете ли вы библиотеку Awaility в своей работе?
🔥7👍4
Про AssertJ поговорили, про Mockito тоже поговорили, даже Awaitility затронули. Значит, пришло время поговорить про WireMock!
WireMock — это инструмент, который поднимает локальный HTTP-сервер прямо внутри теста (или отдельным процессом) и позволяет:
• эмулировать внешние API;
• контролировать ответы (и их задержки);
• проверять, что запросы отправлялись с правильными параметрами;
• работать без реального интернета.
Минимальный пример без расширений для JUnit и другой автоматизации:
Существенную часть здесь занимает настройка самого WireMock-а, которую можно существенно упростить, если добавить расширение для JUnit:
Мне очень нравятся библиотеки, в которых API читаемый. Мне кажется, WireMock в этом плане - одна из лучших библиотек, настраивать сам WireMock одно удовольствие. Настраивать ответы, которые WireMock выдает уже не так просто, но API довольно выразительный и старается помочь изо всех сил.
Самый простой вариант - отвечать заранее заготовленным текстом и статусом:
Отвечать ошибкой:
Отвечать с задержкой:
И, наконец, динамическая генерация ответа на основе данных из запроса:
Кроме настройки запросов, WireMock позволяет проверять, какой именно запрос пришел (вдруг, не все параметры указаны). Самые простые варианты:
Как и в случае с Mockito, можно проверять, сколько было запросов:
Можно проверить наличие заголовка, например, что указан правильный content type:
WireMock — это инструмент, который поднимает локальный HTTP-сервер прямо внутри теста (или отдельным процессом) и позволяет:
• эмулировать внешние API;
• контролировать ответы (и их задержки);
• проверять, что запросы отправлялись с правильными параметрами;
• работать без реального интернета.
Минимальный пример без расширений для JUnit и другой автоматизации:
import com.github.tomakehurst.wiremock.WireMockServer;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
public class WireMockExampleTest {
private WireMockServer wireMockServer;
@BeforeEach
void setup() {
wireMockServer = new WireMockServer(8089);
wireMockServer.start();
configureFor("localhost", 8089);
stubFor(get(urlEqualTo("/hello"))
.willReturn(aResponse()
.withStatus(200)
.withBody("Hello from WireMock!")));
}
@AfterEach
void tearDown() {
wireMockServer.stop();
}
@Test
void shouldReturnStubbedResponse() {
String response = new RestTemplate()
.getForObject("http://localhost:8089/hello", String.class);
assertThat(response).isEqualTo("Hello from WireMock!");
}
}
Существенную часть здесь занимает настройка самого WireMock-а, которую можно существенно упростить, если добавить расширение для JUnit:
public class WireMockJUnit5Test {
// вот здесь расширение и регистрируется
@RegisterExtension
static WireMockExtension wireMock = WireMockExtension.newInstance()
.options(wireMockConfig().port(8089))
.build();
// обычный тест
@Test
void shouldReturnStubbedResponse() {
wireMock.stubFor(get(urlEqualTo("/hello"))
.willReturn(aResponse()
.withStatus(200)
.withBody("Hello JUnit 5 + WireMock!")));
String response = new RestTemplate()
.getForObject("http://localhost:8089/hello", String.class);
assertThat(response).isEqualTo("Hello JUnit 5 + WireMock!");
wireMock.verify(getRequestedFor(urlEqualTo("/hello")));
}
}
Мне очень нравятся библиотеки, в которых API читаемый. Мне кажется, WireMock в этом плане - одна из лучших библиотек, настраивать сам WireMock одно удовольствие. Настраивать ответы, которые WireMock выдает уже не так просто, но API довольно выразительный и старается помочь изо всех сил.
Самый простой вариант - отвечать заранее заготовленным текстом и статусом:
stubFor(get(urlEqualTo("/hello"))
.willReturn(aResponse()
.withStatus(200)
.withBody("Hello from WireMock!")));
Отвечать ошибкой:
stubFor(get("/error")
.willReturn(serverError())); // статус 500
Отвечать с задержкой:
stubFor(get("/slow")
.willReturn(aResponse()
.withFixedDelay(2000) // задержка 2 секунды
.withStatus(200)
.withBody("Delayed!")));
И, наконец, динамическая генерация ответа на основе данных из запроса:
stubFor(get(urlMatching("/user/.*"))
.willReturn(aResponse()
.withStatus(200)
.withTransformers("response-template")
.withBody("Hello {{request.path.[1]}}!")));
Кроме настройки запросов, WireMock позволяет проверять, какой именно запрос пришел (вдруг, не все параметры указаны). Самые простые варианты:
// проверим, что пришел GET запрос
verify(getRequestedFor(urlEqualTo("/hello")));
// проверим, что пришел POST запрос
verify(postRequestedFor(urlEqualTo("/api/data")));
Как и в случае с Mockito, можно проверять, сколько было запросов:
verify(2, getRequestedFor(urlEqualTo("/hello")));
Можно проверить наличие заголовка, например, что указан правильный content type:
verify(postRequestedFor(urlEqualTo("/submit"))
.withHeader("Content-Type", equalTo("application/json")));
🔥8👍3
Или, что в теле запроса есть JSON в определенном формате:
Аналогично, можно проверять и GET запросы на наличие параметров:
Какими еще возможностями WireMock вы пользуетесь?
verify(postRequestedFor(urlEqualTo("/submit"))
.withRequestBody(matchingJsonPath("$.user.id", equalTo("123"))));
Аналогично, можно проверять и GET запросы на наличие параметров:
verify(getRequestedFor(urlPathEqualTo("/list"))
.withQueryParam("page", equalTo("3")));
Какими еще возможностями WireMock вы пользуетесь?
🔥8👍4
Test Containers Shorts
Test Containers — это библиотека для запуска Docker-контейнеров прямо из тестов и сегодня будет короткая заметка об этой библиотеке.
Для примера запустим PostgreSQL прямо из теста (пока без интеграции с JUnit):
Для JUnit есть расширение, которое автоматически запускает и останавливает контейнеры до и после выполнения теста:
Test Containers довольно удобно интегрируются со спринговыми тестами через
А вы используете Test Containers в своей работе?
Test Containers — это библиотека для запуска Docker-контейнеров прямо из тестов и сегодня будет короткая заметка об этой библиотеке.
Для примера запустим PostgreSQL прямо из теста (пока без интеграции с JUnit):
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import static org.assertj.core.api.Assertions.assertThat;
class PostgresTest {
// Поднимаем контейнер PostgreSQL
static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
static {
postgres.start();
}
@Test
void testDatabaseConnection() throws Exception {
var connection = postgres.createConnection("");
var result = connection.createStatement().executeQuery("SELECT 1");
assertThat(result.next()).isTrue();
assertThat(result.getInt(1)).isEqualTo(1);
}
}
Для JUnit есть расширение, которое автоматически запускает и останавливает контейнеры до и после выполнения теста:
@Testcontainers
class JUnit5Test {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
@Test
void testIsRunning() {
assertThat(postgres.isRunning()).isTrue();
}
}
Test Containers довольно удобно интегрируются со спринговыми тестами через
@DynamicPropertySource:
@SpringBootTest
@Testcontainers
class UserRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void overrideProps(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private UserRepository userRepository;
@Test
void testSaveAndFindUser() {
User user = new User(null, "Alice");
userRepository.save(user);
var users = userRepository.findAll();
assertThat(users).hasSize(1);
assertThat(users.get(0).getName()).isEqualTo("Alice");
}
}
А вы используете Test Containers в своей работе?
👍10🔥6
Media is too big
VIEW IN TELEGRAM
Hibernate vs jOOQ vs SQL - Engineering Trade-offs
Hibernate долго был для меня инструментом по умолчанию в Java-разработке — быстрый, удобный, “магический”. Но со временем я стал всё чаще замечать его ограничения и переходить к jOOQ или plain SQL.
В этом видео мой взгляд на ORM глазами инженера, который работал с реальными системами: где Hibernate помогает, а где начинает усложнять жизнь.
Hibernate долго был для меня инструментом по умолчанию в Java-разработке — быстрый, удобный, “магический”. Но со временем я стал всё чаще замечать его ограничения и переходить к jOOQ или plain SQL.
В этом видео мой взгляд на ORM глазами инженера, который работал с реальными системами: где Hibernate помогает, а где начинает усложнять жизнь.
🔥14❤2
Media is too big
VIEW IN TELEGRAM
Hibernate - Спасение или проблема?
Hibernate — это мощный ORM, который идеально раскрывается в бизнес-приложениях: CRUD, сложные доменные модели, связи между сущностями и быстрый старт разработки без постоянного написания SQL.
В этом видео разбираем:
— где Hibernate реально помогает
— почему он так хорош для CRUD и агрегатов
— как работает lazy loading и где он может «стрельнуть в ногу»
— когда JPQL уже не хватает и приходится идти в native SQL
— и где проходит граница между удобством ORM и контролем над базой
А как ты используешь Hibernate в своих проектах?
Hibernate — это мощный ORM, который идеально раскрывается в бизнес-приложениях: CRUD, сложные доменные модели, связи между сущностями и быстрый старт разработки без постоянного написания SQL.
В этом видео разбираем:
— где Hibernate реально помогает
— почему он так хорош для CRUD и агрегатов
— как работает lazy loading и где он может «стрельнуть в ногу»
— когда JPQL уже не хватает и приходится идти в native SQL
— и где проходит граница между удобством ORM и контролем над базой
А как ты используешь Hibernate в своих проектах?
🔥7
Media is too big
VIEW IN TELEGRAM
Почему я всё реже выбираю Hibernate
В этом видео расскажу, где ORM создаёт больше проблем, чем решает, когда стоит использовать чистый SQL и почему jOOQ стал моим инструментом по умолчанию для работы с базой данных в Java.
В этом видео расскажу, где ORM создаёт больше проблем, чем решает, когда стоит использовать чистый SQL и почему jOOQ стал моим инструментом по умолчанию для работы с базой данных в Java.
👍8❤7