Статьи

JUnit 5 — первый взгляд на следующее поколение JUnit

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

Я взял последнюю версию для вращения и записал изменения, которые я нашел здесь заслуживающими внимания.

Установка JUnit 5

Вероятно, нет нужды говорить, что для проекта под названием JUnit Lambda требуется Java 1.8 или более поздняя версия. Если это дано, то включить библиотеку просто. Последняя версия текущего альфа-релиза доступна в репозитории снимков Sonatype по адресу https://oss.sonatype.org/content/repositories/snapshots/org/junit/.

Артефакты можно использовать вместе с Maven и Gradle. Если вы предпочитаете поддерживать зависимости вручную, существует также zip-дистрибутив, который содержит все для компиляции и запуска JUnit 5.

Во время разработки достаточно зависеть от org.junit:junit5-api .

Обратите внимание, что при указании репозитория моментальных снимков он должен быть настроен так, чтобы никогда не кэшировать артефакты, чтобы всегда использовалась последняя версия.

Свободная резка от JUnit 4

Насколько я вижу, новая версия представляет собой полную переписку библиотеки без каких-либо зависимостей от более старых версий. Таким образом, вы можете насладиться традиционным бесплатным тестированием (хотя бы на время ;-).

Но, конечно, есть путь миграции, который позволяет сосуществовать обеим версиям и позволит вам поддерживать существующую базу тестового кода при написании новых тестов с JUnit 5. Подробнее об этом позже.

То же самое, но другое

Но давайте наконец посмотрим, как выглядят тесты JUnit 5. На первый взгляд мало что изменилось. Простой тестовый класс …

1
2
3
4
5
6
class FirstTests {
  @Test
  void firstTest() {
    fail();
  }
}

… едва ли можно отличить от теста JUnit 4.

Но вы заметили небольшую разницу? Да, тесты больше не должны быть общедоступными, но, если вы предпочитаете, все еще может быть, конечно.

Хотя аннотации все еще используются для определения методов настройки и разрушения тестовой среды, их имена изменились. То, что было @BeforeClass/AfterClass , теперь называется @BeforeAll/AfterAll а @Before/After теперь называется @BeforeEach/AfterEach .

Игнорирование тестов также все еще возможно с аннотацией @Disabled .

@Test vs. @Test

Как вы уже видели, тесты все еще помечены аннотацией @Test . Но будьте осторожны, если у вас также есть JUnit 4 на вашем пути к классам. JUnit 5 имеет собственную аннотацию @Test , поэтому обязательно импортируйте org.junit.gen5.api.Test который является правильным. В противном случае тестер JUnit 5 не найдет ваши тесты.

Следует также отметить, что новая аннотация @Test не предлагает другие услуги. Если вы привыкли использовать время timeout или expected время от времени, вам нужно будет заменить их в JUnit 5.

Запуск тестов с помощью JUnit 5

Неудивительно, что пока нет поддержки IDE для запуска тестов JUnit 5. Поэтому я использовал ConsoleRunner для выполнения своих экспериментов. Для запуска тестов требуется еще три модуля:

  • org.junit:junit5-engine
  • org.junit:junit-launcher
  • org.junit:junit-console

Моя IDE — Eclipse, и для запуска тестов с ConsoleRunner мне пришлось вручную расширить Classpath конфигурации запуска. Только после добавления выходной папки test-classes которая содержит скомпилированные тесты, они будут выбраны. Но эта странность также может быть связана с моим скудным знанием Maven или с особенностью интеграции Eclipse Maven.

Команда JUnit 5 также предоставляет базовые плагины для выполнения тестов в сборках Maven и Gradle. См. Главу « Поддержка сборки», если вы хотите попробовать их.

Утверждения

На первый взгляд утверждения не сильно изменились, за исключением того, что теперь они размещены в классе org.junit.gen5.api.Assertions .

Но при ближайшем рассмотрении выясняется, что assertThat() исчез, а вместе с ним и неудачная зависимость от Hamcrest. Эти методы фактически дублировали API, предоставляемый MatcherAssert и привязывали предыдущие версии JUnit к библиотеке Hamcrest. Эта зависимость иногда приводила к конфликтам разрешения классов. В частности, при использовании с другими библиотеками, что еще хуже, они включают копию Hamcrest самостоятельно.

Другое изменение — новый assertAll() , предназначенный для группировки утверждений. Например

1
2
3
4
assertAll( "names", () -> {
  assertEquals( "John", person.getFirstName() );
  assertEquals( "Doe", person.getLastName() );
} );

сообщит об MultipleFailuresError содержащей все ошибочные утверждения в группе.

В этом случае ответственные исполнители должны надлежащим образом отобразить эту ошибку. Однако текущая реализация ConsoleRunner еще не учитывает сгруппированные сбои и просто сообщает о первом:

1
2
3
Finished:    testNames [junit5:com...GroupAssertionsTest#testNames()]
             => Exception: names (1 failure)
             expected: <John> but was: <Mary>

Моя первая нефильтрованная мысль состояла в том, что, если нужны группировки утверждений, это может быть признаком разделения кода на несколько тестов. Но я еще не использовал сгруппированные утверждения по-настоящему, и могут быть места, где они имеют смысл.

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

Тестирование исключений было унифицировано. Чтобы заменить expected и ExpectedException expectThrows теперь существует утверждение expectThrows которое оценивает лямбда-выражение и проверяет, что оно выдает исключение данного типа.

Например,

1
2
3
4
5
6
7
8
@Test
void testException() {
  Foo foo = new Foo();
 
  Throwable exception = expectThrows( IllegalStateException.class, foo::bar );
     
  assertEquals( "some message", exception.getMessage() );
}

IllegalStateException неудачу, если вызов foo::bar() не IllegalStateException . В противном случае выброшенное исключение будет возвращено и может быть дополнительно проверено. Если выброшенное исключение не представляет интереса, существует также метод assertThrows() который возвращает void.

Прощай, Бегун, Правило и ClassRule

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

Расширения могут использоваться декларативно путем аннотирования тестового класса или тестового метода с помощью @ExtendWith . Например, тест, который хочет инициализировать некоторые поля с помощью макетов, может использовать расширение Mockito, например:

1
2
3
4
5
6
7
8
9
@ExtendWith(MockitoExtension.class)
class MockedTest {
 
  @Mock
  Person person;
   
  // ...
   
}

Если вас интересует больше этой темы, следите за отдельной статьей о расширениях и о том, как перенести существующие правила в пользовательские расширения, которые я планирую написать.

Параметры метода испытаний

В JUnit 5 методам теперь разрешено иметь параметры. Это позволяет вводить зависимости на уровне метода.

Чтобы предоставить параметр, необходим так называемый распознаватель , расширение, которое реализует MethodParameterResolver . Как и во всех других расширениях, чтобы использовать определитель для данного метода или класса, его необходимо объявить с помощью @ExtendWith . Есть также два встроенных распознавателя, которые не нужно явно объявлять. Они предоставляют параметры типа TestInfo и TestReporter .

Например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
class MethodParametersTest {
 
  @Test
  // implicitly uses TestInfoParameterResolver to provide testInfo
  void testWithBuiltIntParameterResolver( TestInfo testInfo ) {
    // ...
  }
 
  @Test
  @ExtendWith( CustomEnvironmentParameterResolver.class )
  // explicit resolver declared, could also be placed at class level
  void testWithCustomParameterResolver( CustomEnvironment environment ) {
    // ...
  }
}

Если во время выполнения не найден соответствующий сопоставитель параметров, двигатель не проходит тест с соответствующим сообщением.

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

Обратная совместимость

Для преодоления разрыва до тех пор, пока среды IDE не будут поддерживать JUnit 5 изначально, существует JUnit 4 Runner, который может выполнять тесты, написанные для JUnit 5. Используйте аннотацию @RunWith(JUnit5.class) для запуска тестовых классов и наборов тестов.

С помощью этого раннера можно одновременно запускать классы тестирования JUnit 4 и 5. То, что, безусловно, выходит за рамки видимости, — это смешение старых и новых концепций в одном тесте, например, сосуществование @Rule s с @ExtendWith и т.п.

Тестовые утилиты, такие как Mockito и AssertJ, продолжат работать с новой версией без изменений. Они взаимодействуют с JUnit, вызывая исключение, которое все еще считается неудачным тестом, даже в JUnit 5 🙂

Открытый тестовый альянс для JVM

Команда JUnit Lambda также создала открытый тестовый альянс для JVM с целью создания стандарта, облегчающего взаимодействие между тестовыми средами, библиотеками утверждений, библиотеками-макетами, инструментами сборки и IDE.

Основная цель — предоставить библиотеку, которая определяет общий набор исключений, которые будут использоваться при тестировании сред (например, JUnit, TestNG, Spock и т. Д.), А также библиотеки утверждений. Инструменты сборки и IDE также выиграют в том, что они могут полагаться на один и тот же набор типов независимо от среды тестирования.

org.opentest4j реализация доступна в виде библиотеки org.opentest4j , которая, как вы догадываетесь, используется в JUnit 5.

прогноз

У меня сложилось впечатление, что основные понятия новой версии установлены. Такие вещи, как @Test, настройка и удаление аннотаций, концепция модели с одним расширением, вероятно, сохранит свою текущую форму.

Но многие детали, похоже, еще не решены, и API, скорее всего, изменятся, что, я думаю, вполне понятно на данном этапе цикла разработки. Каждая часть API помечена аннотацией @API которая указывает, насколько она стабильна.

Если эта публикация заинтересовала вас, и вы, возможно, захотите просмотреть документацию для получения дополнительной информации, есть еще много интересного, например:

Первый этап запланирован на конец первого квартала 2016 года. Предварительный список вопросов, которые будут рассмотрены в этом выпуске, доступен здесь .

Ссылка: JUnit 5 — первый взгляд на следующее поколение JUnit от нашего партнера по JCG Рудигера Херрманна в блоге Code Affine .