Статьи

Еще один способ обработки исключений в JUnit: catch-exception

Есть много способов обработки исключений в JUnit ( 3 способа обработки исключений в JUnit. Какой из них выбрать?, Правило JUnit ExpectedException: помимо основ ). В этой статье я представлю библиотеку catch-exception, которую мне рекомендовали попробовать. Короче говоря, catch-exception — это библиотека, которая перехватывает исключения в одной строке кода и делает их доступными для дальнейшего анализа.

Установить через Maven

Чтобы быстро начать работу, я использовал свой демонстрационный проект Unit Testing с набором тестовых зависимостей ( JUnit, Mocito, Hamcrest, AssertJ ) и добавил исключения- ловушки :

1
2
3
4
5
6
<dependency>
    <groupId>com.googlecode.catch-exception</groupId>
    <artifactId>catch-exception</artifactId>
    <version>1.2.0</version>
    <scope>test</scope>
</dependency>

Таким образом, дерево зависимостей выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
[INFO] --- maven-dependency-plugin:2.1:tree @ unit-testing-demo ---
[INFO] com.github.kolorobot:unit-testing-demo:jar:1.0.0-SNAPSHOT
[INFO] +- org.slf4j:slf4j-api:jar:1.5.10:compile
[INFO] +- org.slf4j:jcl-over-slf4j:jar:1.5.10:runtime
[INFO] +- org.slf4j:slf4j-log4j12:jar:1.5.10:runtime
[INFO] +- log4j:log4j:jar:1.2.15:runtime
[INFO] +- junit:junit:jar:4.11:test
[INFO] +- org.mockito:mockito-core:jar:1.9.5:test
[INFO] +- org.assertj:assertj-core:jar:1.5.0:test
[INFO] +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] +- org.objenesis:objenesis:jar:1.3:test
[INFO] \- com.googlecode.catch-exception:catch-exception:jar:1.2.0:test

Начиная

Тестируемая система (SUT):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
class ExceptionThrower {
  
    void someMethod() {
        throw new RuntimeException("Runtime exception occurred");
    }
  
    void someOtherMethod() {
        throw new RuntimeException("Runtime exception occurred",
                new IllegalStateException("Illegal state"));
    }
  
    void yetAnotherMethod(int code) {
        throw new CustomException(code);
    }
}

Пример базового подхода в стиле BDD для ловли с исключениями AssertJ :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
import org.junit.Test;
  
import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionAssertJ.*;
  
public class CatchExceptionsTest {
  
    @Test
    public void verifiesTypeAndMessage() {
        when(new SomeClass()).someMethod();
  
        then(caughtException())
                .isInstanceOf(RuntimeException.class)
                .hasMessage("Runtime exception occurred")
                .hasMessageStartingWith("Runtime")
                .hasMessageEndingWith("occured")
                .hasMessageContaining("exception")
                .hasNoCause();              
    }
}

Выглядит неплохо. Кратко, читабельно. Нет бегунов JUnit. Обратите внимание, что я указал, какой метод SomeClass я ожидаю, чтобы SomeClass исключение. Как вы можете себе представить, я могу проверить несколько исключений в одном тесте. Хотя я бы не рекомендовал такой подход, так как может показаться, что он нарушает одну обязанность теста.

Кстати, если вы работаете с Eclipse, это может быть полезно для вас: улучшите поддержку содержимого для типов со статическими членами при создании тестов JUnit в Eclipse

Проверьте причину

Я думаю, что нет никакого комментария, необходимого для приведенного ниже кода:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
import org.junit.Test;
  
import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionAssertJ.*;
  
public class CatchExceptionsTest {
  
    @Test
    public void verifiesCauseType() {
        when(new ExceptionThrower()).someOtherMethod();
        then(caughtException())
                .isInstanceOf(RuntimeException.class)
                .hasMessage("Runtime exception occurred")
                .hasCauseExactlyInstanceOf(IllegalStateException.class)
                .hasRootCauseExactlyInstanceOf(IllegalStateException.class);
    }
}

Проверьте пользовательское исключение с Hamcrest

Для проверки пользовательского исключения я использовал код соответствия Hamcrest из моего предыдущего поста :

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
31
32
33
34
35
36
37
class CustomException extends RuntimeException {
    private final int code;
  
    public CustomException(int code) {
        this.code = code;
    }
  
    public int getCode() {
        return code;
    }
}
  
class ExceptionCodeMatches extends TypeSafeMatcher<CustomException> {
  
    private int expectedCode;
  
    public ExceptionCodeMatches(int expectedCode) {
        this.expectedCode = expectedCode;
    }
  
    @Override
    protected boolean matchesSafely(CustomException item) {
        return item.getCode() == expectedCode;
    }
  
    @Override
    public void describeTo(Description description) {
        description.appendText("expects code ")
                .appendValue(expectedCode);
    }
  
    @Override
    protected void describeMismatchSafely(CustomException item, Description mismatchDescription) {
        mismatchDescription.appendText("was ")
                .appendValue(item.getCode());
    }
}

И тест:

01
02
03
04
05
06
07
08
09
10
11
12
13
import org.junit.Test;
  
import static com.googlecode.catchexception.CatchException.*;
import static org.junit.Assert.*;
  
public class CatchExceptionsTest {
  
    @Test
    public void verifiesCustomException() {
        catchException(new ExceptionThrower(), CustomException.class).yetAnotherMethod(500);
        assertThat((CustomException) caughtException(), new ExceptionCodeMatcher(500));
    }
}

Резюме

ловушка-исключение выглядит действительно хорошо. Это легко начать быстро. Я вижу некоторые преимущества по сравнению с правилом метода в JUnit. Если у меня будет возможность, я буду более тщательно исследовать библиотеку, надеюсь, в реальном проекте.

В случае, если вы заинтересованы, пожалуйста, посмотрите на мои другие сообщения: