Зачем тестировать потоки исключений? Как и со всем вашим кодом, тестовое покрытие записывает контракт между вашим кодом и бизнес-функциями, которые код должен производить, оставляя вам живую документацию кода вместе с добавленной способностью подчеркивать функциональность рано и часто. Я не буду вдаваться во многие преимущества тестирования, а сосредоточусь только на тестировании исключений.
Есть много способов проверить поток исключений, созданный из фрагмента кода. Допустим, у вас есть защищенный метод, который требует, чтобы аргумент не был нулевым. Как бы вы проверили это состояние? Как вы удерживаете JUnit от сообщения об ошибке при возникновении исключения? В этом блоге рассматриваются несколько различных методов, кульминацией которых является ExpectedException в JUnit, реализованная с помощью функциональности JUnit @Rule .
По старому
В недалеком прошлом процесс тестирования исключения требовал большого количества стандартного кода, в котором вы бы запустили блок try / catch, сообщили об ошибке, если ваш код не дал ожидаемого поведения, а затем перехватили исключение, ища конкретный тип. Вот пример:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
public class MyObjTest { @Test public void getNameWithNullValue() { try { MyObj obj = new MyObj(); myObj.setName( null ); fail( 'This should have thrown an exception' ); } catch (IllegalArgumentException e) { assertThat(e.getMessage().equals( 'Name must not be null' )); } } } |
Как вы можете видеть из этого старого примера, многие строки в тестовом примере просто служат для того, чтобы поддержать отсутствие функциональных возможностей, специально предназначенных для тестирования обработки исключений. Хорошим моментом для метода try / catch является возможность проверки конкретного сообщения и любых пользовательских полей в ожидаемом исключении. Мы рассмотрим это чуть ниже с помощью аннотации ExpectedException и @Rule в JUnit.
JUnit добавляет ожидаемые исключения
JUnit ответил на потребность пользователей в обработке исключений, добавив поле аннотации @Test «ожидаемый». Предполагается, что весь тестовый пример пройдет, если тип выданного исключения соответствует классу исключения, присутствующему в аннотации.
1
2
3
4
5
6
7
8
|
public class MyObjTest { @Test (expected = IllegalArgumentException. class ) public void getNameWithNullValue() { MyObj obj = new MyObj(); myObj.setName( null ); } } |
Как вы можете видеть из более нового примера, кода котельной плиты немного меньше, и тест очень лаконичен, однако есть несколько недостатков . Основным недостатком является то, что условия испытаний слишком широки. Предположим, у вас есть две переменные в сигнатуре, и обе не могут быть нулевыми, тогда как вы узнаете, для какой переменной было выброшено исключение IllegalArgumentException? Что происходит, когда вы расширили Throwable и вам нужно проверить наличие поля? Имейте это в виду, когда будете читать дальше, решения будут следовать.
JUnit @Rule и ExpectedException
Если вы посмотрите на предыдущий пример, вы можете увидеть, что вы ожидаете исключения IllegalArgumentException, но что если у вас есть пользовательское исключение? Что если вы хотите убедиться, что сообщение содержит определенный код ошибки или сообщение? Вот где JUnit действительно преуспел, предоставив объект JUnit @Rule, специально предназначенный для тестирования исключений. Если вы не знакомы с JUnit @Rule, прочитайте документы здесь .
ExpectedException
JUnit предоставляет класс ExpectedException класса JUnit, предназначенный для использования в качестве @Rule. ExpectedException позволяет вашему тесту объявить, что ожидается исключение, и предоставляет вам некоторые базовые встроенные функциональные возможности для четкого выражения ожидаемого поведения. В отличие от аннотации @Test (ожидаемая), класс ExpectedException позволяет проверять определенные сообщения об ошибках и настраиваемые поля с помощью библиотеки соответствий Hamcrest .
Пример ожидаемого исключения JUnit
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
import org.junit.rules.ExpectedException; public class MyObjTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void getNameWithNullValue() { thrown.expect(IllegalArgumentException. class ); thrown.expectMessage( 'Name must not be null' ); MyObj obj = new MyObj(); obj.setName( null ); } } |
Как я уже говорил выше, инфраструктура позволяет вам тестировать определенные сообщения, гарантируя, что выбрасываемое исключение — это тот случай, когда тест специально ищет. Это очень полезно, когда вопрос об отмене нескольких аргументов под вопросом.
Настраиваемые поля
Возможно, наиболее полезной особенностью платформы ExpectedException является возможность использовать сопоставители Hamcrest для тестирования ваших пользовательских / расширенных исключений. Например, у вас есть пользовательское / расширенное исключение, которое должно быть вызвано в методе, а внутри исключения есть errorCode. Как вы тестируете эту функциональность, не вводя код пластины котла из блока try / catch, указанного выше? Как насчет пользовательского Matcher!
Этот код доступен по адресу: https://github.com/mike-ensor/custom-exception-testing
Решение: сначала тестовый пример
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
import org.junit.rules.ExpectedException; public class MyObjTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void someMethodThatThrowsCustomException() { thrown.expect(CustomException. class ); thrown.expect(CustomMatcher.hasCode( '110501' )); MyObj obj = new MyObj(); obj.methodThatThrowsCustomException(); } } |
Решение: Custom matcher
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import com.thepixlounge.exceptions.CustomException; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; public class CustomMatcher extends TypeSafeMatcher<CustomException> { public static BusinessMatcher hasCode(String item) { return new BusinessMatcher(item); } private String foundErrorCode; private final String expectedErrorCode; private CustomMatcher(String expectedErrorCode) { this .expectedErrorCode = expectedErrorCode; } @Override protected boolean matchesSafely( final CustomException exception) { foundErrorCode = exception.getErrorCode(); return foundErrorCode.equalsIgnoreCase(expectedErrorCode); } @Override public void describeTo(Description description) { description.appendValue(foundErrorCode) .appendText( ' was not found instead of ' ) .appendValue(expectedErrorCode); } } |
ПРИМЕЧАНИЕ: Пожалуйста, посетите https://github.com/mike-ensor/custom-exception-testing, чтобы получить копию работающего Hamcrest Matcher, JUnit @Rule и ExpectedException.
И вот он, краткий обзор различных способов тестирования исключений, создаваемых вашим кодом, а также возможность проверки определенных сообщений и полей из пользовательских классов исключений. Пожалуйста, будьте конкретны с вашими тестами и постарайтесь нацелиться именно на тот случай, который вы настроили для теста, помните, что тесты могут уберечь вас от появления ошибок побочных эффектов!
Приятного кодирования и не забудьте поделиться!
Ссылка: тестирование пользовательских исключений с ExpectedException и @Rule от нашего партнера по JCG Майка в блоге сайта Майка .