Тестирование исключений

До сих пор мы тестировали только корректное поведение кода (happy path) — проверяли корректную работу на корректных данных. Такой идеальный мир, к сожалению, встречается только в учебниках, да и то, не во всех. Реальный код сталкивается с кривыми руками программистов данными и ошибками, и должен на них реагировать. Традиционный способ обработки ошибок в Java — исключения и их тоже нужно тестировать.

Подготовка

Возьмём код из примера основной функциональности JUnit и создадим новый класс с тестами  в StringUtilsExceptionsTest.

Простая проверка исключений

Один из методов в StringUtils, toDouble(String), бросает NumberFormatException, если передать в него неправильную строку. Это поведение можно и нужно протестировать:

    @Test(expected = NumberFormatException.class)
    public void testToDoubleException() {
        StringUtils.toDouble(testString);
    }

Параметер expected говорит JUnit что метод должен кинуть исключение указанного типа и это не будет ошибкой теста. А вот если исключение не будет брошено, это будет ошибкой теста.

Простой метод проверки исключений имеет, впрочем, некоторое количество недостатков: во-первых, проверяется только сам факт исключения, но нет возможности проверить связанную с исключением информацию. Второй недостаток следует из первого — нет возможности проверить, какая именно часть кода кинула исключение, в случае если исключение того же самого типа может быть выброшено из разных мест тестируемого метода.

Matchers, Rules, Exceptions

В JUnit предусмотрен более сложный подход, основанный на гибкой и расширяемой системе Rules(правил), которой будет посвящена отдельная статься. Однако воспользоваться одним из Rule, для подробной проверки исключений, достаточно несложно:

    @Rule
    public ExpectedException exception = ExpectedException.none();


    /* ..... */


    @Test
    public void testToDoubleExceptionDeepCheck() {
        exception.expect(NumberFormatException.class);
        exception.expectMessage(containsString(testString));


        StringUtils.toDouble(testString);
    }

Проверка исключений с использованием ExpectedException состоит из двух частей: создание проверяющего Rule и его настройка. Конструируется ExpectedException достаточно очевидным методом:

    @Rule
    public ExpectedException exception = ExpectedException.none();

Важно, чтобы экземпляр объект Rule был объявлен как public, поскольку JUnit использует reflection для вызова кода Rule.

Настройка Rule производится к каждом тесте отдельно. Состояние ExpectedException сбрасывается перед каждым тестом (об этом говорит аннотация @Rule). Можно указать какое именно исключение ожидается, проверить его поля message и cause.

Проверочные значения можно задавать как напрямую, так и используя Matcher’ы, что делает проверку ещё гибче:

    @Test
    public void testToDoubleExceptionDeepCheck() {
        exception.expect(NumberFormatException.class);
        exception.expectMessage(containsString(testString));


        StringUtils.toDouble(testString);
    }

try/catch

В совсем сложных случаях, когда, например, надо проверить содержимое исключения вашего собственного типа, можно использовать существующий ещё с JUnit3 подход try/catch/fail (а можно расширить ExpectedException). В этом случае вы вручную перехватываете исключение, делаете с ним всё что пожелаете, а если исключение не было выкинуто, вручную же проваливаете тест:

    @Test
    public void testToDoubleExceptionManual() {
        try {
            StringUtils.toDouble(testString);
            fail("Expected NumberFormatException");
        } catch (NumberFormatException ex) {
            assertThat(ex.getMessage(), containsString(testString));
        }
    }

try/catch часть кода очевидна — вызывается метод, который кидает исключение, исключение перехватывается и тестируется. Но, после вызова метода стоит вызов fail(), который вручную проваливает тест, если исключение не было брошено. Разумеется, fail()  можно использовать в любом удобном случае, когда требуется провалить тест вручную.

Скачать код примера

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *