В статье мы узнаем:
1. Как выбрасывать исключение в пустом классе «Optional».
2. Как тестировать и просматривать исключение.
3. Как использовать ошибки утверждения.
1. Как выбрасывать исключение в пустом классе «Optional»
Не следует использовать ifPresentOrElse
для исключений. Вот как это обычно происходит (плохой пример применения пользовательских исключений времени выполнения с ifPresentOrElse
):
dao.findExample(id).ifPresentOrElse(this::workWithExample, () -> throw new CustomRuntimeException());
В чем проблема с этим фрагментом кода?
- Выбрасывает только непроверяемые исключения.
emptyAction
выбрасывает только RuntimeException
. В качестве примера можно привести NullPointerException
(исключение нулевого указателя), когда значение или действие отсутствует. Runnable
выбрасывает непроверяемые исключения и не предназначен для работы с проверяемыми исключениями.
- Исключения усложняют
ifPresentOrElse
.
Сценарии использования emptyAction
не должны предусматривать выбрасывание исключений. Выбрасывание исключений из emptyAction
приводит к усложнению кода.
У этой проблемы есть два решения.
Первое — использовать if
для проверки наличия Optional
. Затем выбросить исключение в ветви else
.
Более лучшим решением будет orElseThrow
. Необходимо указать поставщика исключений, например конструктор исключений.
Вот как правильно выбрасывать пользовательские проверяемые исключения:
dao.findExample(id).orElseThrow(ExampleNotFoundException::new);
2. Как тестировать и просматривать исключение
Исключения тестируют несколькими способами. Решение зависит от используемой версии JUnit и от того, что именно нужно протестировать.
Один из вариантов — задействовать expected
, работающий с JUnit 4. Но из JUnit 5 он был убран. (Хотя это неточно: expected
все еще доступен в org.junit.Tes
t, но его никогда не существовало нигде в org.junit.jupiter
).
Вокруг expected
много споров. В связи с последними изменениями его убирают. То есть придется провести рефакторинг всех тестов, где используется expected
. А это большая работа, так что лучше подготовиться к JUnit 5.
Использование expected
казалось более чистым, и при этом создаются подробные тесты. Однако с ним нельзя просматривать исключения и нельзя тестировать более одного исключения. Поэтому возникла потребность в чем-то лучшем. Вот как выглядит expected
внутри аннотации теста:
@Test(expected = CustomException.class)
public void test() {
shouldThrowCustomException();
}
Что же использовать с последней версией JUnit после того, как от туда был убран expected
? Метод assertThrows
. Вот пример того, как в более новых версиях JUnit используется assertThrows
:
@Test
void exceptionTesting() {
CustomException thrown = assertThrows(
MyException.class,
this::shouldThrowCustomException,
"Expected shouldThrowCustomException to throw, but it didn't"
);
assertTrue(thrown.getMessage().contains("Your Custom Message"));
}
Перехват исключения осуществляется после вызова исполняемого файла. Пользовательское исключение — первый аргумент — подтверждает ожидаемый тип исключения. Исполняемый файл — второй аргумент — выбрасывает исключение. Сообщение об ошибке — третий аргумент — выводится, если исключение не было выброшено.
Более подробную информацию об assertThrows
можно найти здесь.
А как быть, если вы не используете JUnit 5? Задействуйте в этом случае идиому try-catch
. Она применяется как альтернатива, и до появления expected
обычно использовали ее.
Никакого фактического преимущества в сравнении с этим решением использование expected
не дает. Разве что получается меньше строк кода, вот и все. При таком подходе есть возможность просматривать исключение. В то время как с expected
этого делать нельзя. Используйте этот подход, с ним будет легче перейти на JUnit 5. До появления assertThrows
такой подход был нормой, и даже сегодня это хорошая альтернатива:
// источник - https://github.com/junit-team/junit4/wiki/Exception-testing#trycatch-idiom
@Test
public void testExceptionMessage() {
List<Object> list = new ArrayList<>();
try {
list.get(0);
fail("Expected an IndexOutOfBoundsException to be thrown");
} catch (IndexOutOfBoundsException anIndexOutOfBoundsException) {
assertThat(anIndexOutOfBoundsException.getMessage(), is("Index: 0, Size: 0"));
}
}
Еще одной альтернативой является ExpectedException
. Она подойдет для работы с более старыми версиями JUnit (JUnit < 4.13
). Для тестирования исключения надо добавить аннотацию Rule
. Вот пример (источник):
// источник - https://github.com/junit-team/junit4/wiki/Exception-testing#expectedexception-rule
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void shouldTestExceptionMessage() throws IndexOutOfBoundsException {
List<Object> list = new ArrayList<Object>();
thrown.expect(IndexOutOfBoundsException.class);
thrown.expectMessage("Index: 0, Size: 0");
list.get(0); // выполнение никогда не пойдет дальше этой строки
}
А вот мне аннотация Rule
совсем не нравится. Почему здесь должна быть именно она? Только вводит в заблуждение неискушенных пользователей, даже имея более чистый код. ExpectedException
в более новых версиях JUnit уже не используется.
3. Как использовать ошибки утверждения
Утверждения применяют во многих случаях: при тестировании, в тестовых сценариях. А когда они не выполняются, возникают ошибки утверждения.
Какой самый универсальный подход к утверждениям? Как использовать их вне тестовых сценариев? Следует ли применять ошибки утверждения в обычных классах?
Вот хорошее объяснение (источник):
Ошибки утверждения — это ошибки, а не исключения.
Мы знаем, для чего исключения — для исключительных состояний. Ошибки свидетельствуют о неверных состояниях. Мы знаем, что исключение возникает. И знаем, когда возникает исключение нулевого указателя NullPointerException
. Мы предотвращаем это состояние. Ошибки указывают на ошибку программирования.
Вот пример ошибки утверждения AssertionError
из 2-го издания книги «Java. Эффективное программирование»:
class Example {
private Example() {
throw new AssertionError();
}
}
В Example
имеется закрытый конструктор. Вызывать его ни в коем случае не следует. Если вызов каким-то образом происходит, совершается невозможное действие. Лучший способ передать это — задействовать AssertionError
.
Несколько советов:
- Усвойте разницу между
Errors
(ошибками) иExceptions
(исключениями). Это понимание положительно отразится на коде и облегчит вам жизнь как разработчику. - Просматривайте
Exceptions
(исключения). Такие проверки полезны, вы должны об этом знать. Берите на вооружение новые подходы к просмотру исключений. Разбирайтесь, в чем они лучше, чем старые. - Используйте класс
Optional
правильно: не злоупотребляйте, но и не ограничивайте его применение. Разберитесь с тем, как выбрасывать исключения в пустых классахOptional
.
Читайте также:
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Miloš Živković: 3 Exception Practices To Improve Your Java Skills