Источник: Nuances of Programming
В статье мы узнаем:
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.Test, но его никогда не существовало нигде в 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.
Читайте также:
Перевод статьи Miloš Živković: 3 Exception Practices To Improve Your Java Skills