Статьи

Чистые JUnit Throwable-тесты с лямбдами Java 8

Недавно я участвовал в короткой онлайн-дискуссии в твиттере и google +, которая касалась вопроса, почему появление лямбда-выражений Java 8 делает библиотеку catch-исключений 1 устаревшей. Это было вызвано кратким объявлением о том, что библиотека больше не будет обслуживаться, поскольку лямбды сделают ее избыточной.

Ответ, который я получил в то время, имеет много общего с ответом, представленным Рафалем Боровцем в его хорошо написанном посте « JUNIT: ИСКЛЮЧЕНИЕ ИСПЫТАНИЯ С JAVA 8 И ВЫРАЖЕНИЯМИ LAMBDA» . Однако обдумав оба подхода, я считаю, что можно было бы сделать еще немного лучше в отношении чистого кода.

Таким образом, этот пост является откликом на эту тему, который разделяет мои последние соображения и вкратце объясняет слегка усовершенствованное решение. Таким образом, я надеюсь, скоро узнаю о слабых местах …

мотивация

При написании тестов я всегда стараюсь в конечном итоге получить четкое визуальное разделение фаз аранжировки / действия / утверждения 2 в методе тестирования (и у меня сложилось впечатление, что становится все более и более популярным выделять эти фазы оптически с помощью пустых линии как разделитель).

Теперь мне кажется, что упомянутые выше решения об уловах исключений более или менее смешивают фазы действия и утверждения . Это потому, что оба утверждают, что Throwable был брошен, еще находясь в фазе действия . Но утверждение, по-видимому, относится к фазе утверждения .

К счастью, эту проблему легко решить.

утонченность

Давайте рассмотрим простой пример, чтобы объяснить, как может выглядеть усовершенствованный подход. Я начну с класса, который предоставляет метод, генерирующий IllegalStateException для демонстрации:

1
2
3
4
5
6
7
8
public class Foo {
 
  static final String ERR_MESSAGE = "bad";
 
  public void doIt() throws IllegalStateException {
    throw new IllegalStateException(ERR_MESSAGE);
  }
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class ThrowableCaptor {
 
  public interface Actor {
    void act() throws Throwable;
  }
 
  public static Throwable captureThrowable( Actor actor ) {
    Throwable result = null;
    try {
      actor.act();
    } catch( Throwable throwable ) {
      result = throwable;
    }
    return result;
  }
}

Чтобы подчеркнуть, что ThrowableCaptor используется для обработки фазы действия теста JUnit, метод captorThrowable принимает параметр типа Actor который, по общему признанию, может немного captorThrowable метафору…

В любом случае, с этой утилитой, AssertJ для выражений чистого совпадения, статического импорта и лямбда-выражений Java 8, тест исключения может выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class FooTest {
 
  @Test
  public void testException() {
    // arrange
    Foo foo = new Foo();
     
    // act
    Throwable actual = captureThrowable( foo::doIt );
     
    // assert
    assertThat( actual )
      .isInstanceOf( IllegalStateException.class )
      .hasMessage( Foo.ERR_MESSAGE );
  }
}

Для пояснения я вставил комментарии, чтобы изобразить четкое разделение трех фаз в методе испытаний. Если исключение не выдается, блок assert завершит его с ошибкой подтверждения, отметив, что «Ожидается, что фактическое значение не будет нулевым» 3 .

Вывод

Перемещая Throwable существования Throwable из фазы действия в фазу утверждения, подход «ловить исключения», основанный на лямбда-выражениях Java8, позволяет писать такие тесты довольно чистым способом — по крайней мере, с моей нынешней точки зрения.

Так что ты думаешь? Я что-то упускаю?

  1. Чтобы сделать тестирование исключений более понятным, библиотека catch-exception перехватывает исключения в одной строке кода и делает их доступными для дальнейшего анализа.
  2. См. Практическое модульное тестирование, глава 3.9. Этапы модульного тестирования, Томек Качановский, 2013, часто также обозначается как шаблон сборки-эксплуатации-проверки, Чистый код, Глава 9. Модульные тесты, Роберт С. Мартин, 2009
  3. Проверка Assertion#isNotNull неявно вызывается Assertion#isInstanceOf , но она также может вызываться явно
Ссылка: Очистите JUnit Throwable-тесты с Java 8 Lambdas от нашего партнера JCG Фрэнка Аппеля в блоге Code Affine .