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

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

Подготовка

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

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

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

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

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

Matchers, Rules,

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

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

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

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

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

try/catch

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

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

Исходный код примера доступен на github