Перейти к основному содержимому

Руководство по JUnit 5

· 7 мин. чтения

Задача: Наибольшая подстрока без повторений

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

ANDROMEDA 42

1. Обзор

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

2. Зависимости Maven

Настройка JUnit 5.x.0 довольно проста; нам просто нужно добавить следующую зависимость в наш pom.xml :

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>

Кроме того, теперь есть прямая поддержка запуска модульных тестов на платформе JUnit в Eclipse, а также в IntelliJ. Конечно, мы также можем запускать тесты, используя цель Maven Test.

С другой стороны, IntelliJ по умолчанию поддерживает JUnit 5. Поэтому запустить JUnit 5 на IntelliJ довольно просто. Мы просто щелкаем правой кнопкой мыши -> Выполнить или Ctrl-Shift-F10.

Важно отметить, что для работы этой версии требуется Java 8 .

3. Архитектура

JUnit 5 состоит из нескольких разных модулей из трех разных подпроектов.

3.1. Платформа Юнит

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

Платформа легко интегрирует клиентов с JUnit для обнаружения и выполнения тестов.

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

3.2. Юнит Юпитер

Этот модуль включает новые модели программирования и расширения для написания тестов в JUnit 5. Новые аннотации по сравнению с JUnit 4:

  • @TestFactory — обозначает метод, который является тестовой фабрикой для динамических тестов.
  • @DisplayName — определяет пользовательское отображаемое имя для тестового класса или тестового метода.
  • @Nested — обозначает, что аннотированный класс является вложенным, нестатическим тестовым классом.
  • @Tag — объявляет теги для фильтрации тестов
  • @ExtendWith — регистрирует пользовательские расширения
  • @BeforeEach — обозначает, что аннотированный метод будет выполняться перед каждым тестовым методом (ранее @Before )
  • @AfterEach — означает, что аннотированный метод будет выполняться после каждого тестового метода (ранее @After )
  • @BeforeAll — означает, что аннотированный метод будет выполняться перед всеми тестовыми методами в текущем классе (ранее @BeforeClass )
  • @AfterAll — означает, что аннотированный метод будет выполнен после всех тестовых методов в текущем классе (ранее @AfterClass )
  • @Disable — отключает тестовый класс или метод (ранее @Ignore )

3.3. Юнит Винтаж

JUnit Vintage поддерживает выполнение тестов на основе JUnit 3 и JUnit 4 на платформе JUnit 5.

4. Основные аннотации

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

4.1. @BeforeAll и @BeforeEach

Ниже приведен пример простого кода, который необходимо выполнить перед основными тестовыми примерами:

@BeforeAll
static void setup() {
log.info("@BeforeAll - executes once before all test methods in this class");
}

@BeforeEach
void init() {
log.info("@BeforeEach - executes before each test method in this class");
}

Важно отметить, что метод с аннотацией @BeforeAll должен быть статическим, иначе код не скомпилируется.

4.2. @DisplayName и @Disabled

Теперь давайте перейдем к новым необязательным для теста методам:

@DisplayName("Single test successful")
@Test
void testSingleSuccessTest() {
log.info("Success");
}

@Test
@Disabled("Not implemented yet")
void testShowSomething() {
}

Как мы видим, мы можем изменить отображаемое имя или отключить метод с комментарием, используя новые аннотации.

4.3. @AfterEach и @AfterAll

Наконец, давайте обсудим методы, связанные с операциями после выполнения теста:

@AfterEach
void tearDown() {
log.info("@AfterEach - executed after each test method.");
}

@AfterAll
static void done() {
log.info("@AfterAll - executed after all test methods.");
}

Обратите внимание, что метод с @AfterAll также должен быть статическим.

5. Утверждения и предположения

JUnit 5 пытается в полной мере использовать новые возможности Java 8, особенно лямбда-выражения.

5.1. Утверждения

Утверждения были перемещены в org.junit.jupiter.api.Assertions и значительно улучшены. Как упоминалось ранее, теперь мы можем использовать лямбда-выражения в утверждениях:

@Test
void lambdaExpressions() {
List numbers = Arrays.asList(1, 2, 3);
assertTrue(numbers.stream()
.mapToInt(Integer::intValue)
.sum() > 5, () -> "Sum should be greater than 5");
}

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

Также теперь можно группировать утверждения с помощью assertAll(), который будет сообщать о любых неудачных утверждениях в группе с помощью MultipleFailuresError :

@Test
void groupAssertions() {
int[] numbers = {0, 1, 2, 3, 4};
assertAll("numbers",
() -> assertEquals(numbers[0], 1),
() -> assertEquals(numbers[3], 3),
() -> assertEquals(numbers[4], 1)
);
}

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

5.2. Предположения

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

Мы можем объявить предположение с помощью функций acceptTrue() , acceptFalse() и usingThat( ):

@Test
void trueAssumption() {
assumeTrue(5 > 1);
assertEquals(5 + 2, 7);
}

@Test
void falseAssumption() {
assumeFalse(5 < 1);
assertEquals(5 + 2, 7);
}

@Test
void assumptionThat() {
String someString = "Just a string";
assumingThat(
someString.equals("Just a string"),
() -> assertEquals(2 + 2, 4)
);
}

Если предположение не выполняется, выдается исключение TestAbortedException , и тест просто пропускается.

Предположения также понимают лямбда-выражения.

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

В JUnit 5 есть два способа тестирования исключений, оба из которых мы можем реализовать с помощью метода assertThrows() :

@Test
void shouldThrowException() {
Throwable exception = assertThrows(UnsupportedOperationException.class, () -> {
throw new UnsupportedOperationException("Not supported");
});
assertEquals("Not supported", exception.getMessage());
}

@Test
void assertThrowsException() {
String str = null;
assertThrows(IllegalArgumentException.class, () -> {
Integer.valueOf(str);
});
}

В первом примере проверяются сведения о выброшенном исключении, а во втором — проверяется тип исключения.

7. Наборы тестов

Чтобы продолжить знакомство с новыми функциями JUnit 5, мы рассмотрим концепцию агрегирования нескольких тестовых классов в наборе тестов, чтобы мы могли запускать их вместе. JUnit 5 предоставляет две аннотации, @SelectPackages и @SelectClasses, для создания наборов тестов.

Имейте в виду, что на этом раннем этапе большинство IDE не поддерживают эти функции.

Давайте посмотрим на первый:

@Suite
@SelectPackages("com.foreach")
@ExcludePackages("com.foreach.suites")
public class AllUnitTest {}

@SelectPackage используется для указания имен пакетов, которые будут выбраны при запуске набора тестов. В нашем примере он запустит все тесты. Вторая аннотация, @SelectClasses , используется для указания классов, которые будут выбраны при запуске набора тестов:

@Suite
@SelectClasses({AssertionTest.class, AssumptionTest.class, ExceptionTest.class})
public class AllUnitTest {}

Например, приведенный выше класс создаст набор, содержащий три тестовых класса. Обратите внимание, что классы не обязательно должны быть в одном пакете.

8. Динамические тесты

Последняя тема, которую мы хотим представить, — это функция динамических тестов JUnit 5, которая позволяет нам объявлять и запускать тестовые случаи, созданные во время выполнения. В отличие от статических тестов, которые определяют фиксированное количество тестов во время компиляции, динамические тесты позволяют нам динамически определять тестовые примеры во время выполнения.

Динамические тесты могут быть сгенерированы фабричным методом с аннотацией @TestFactory. Давайте посмотрим на код:

@TestFactory
Stream<DynamicTest> translateDynamicTestsFromStream() {
return in.stream()
.map(word ->
DynamicTest.dynamicTest("Test translate " + word, () -> {
int id = in.indexOf(word);
assertEquals(out.get(id), translate(word));
})
);
}

Этот пример очень простой и понятный. Мы хотим перевести слова с помощью двух ArrayList с именами in и out соответственно. Фабричный метод должен возвращать Stream , Collection , Iterable или Iterator . В нашем случае мы выбрали Java 8 Stream.

Обратите внимание, что методы @TestFactory не должны быть закрытыми или статическими. Количество тестов является динамическим и зависит от размера ArrayList .

9. Заключение

В этой статье мы представили краткий обзор изменений, которые появятся в JUnit 5.

Мы рассмотрели большие изменения в архитектуре JUnit 5 в отношении средства запуска платформы, IDE, других сред модульного тестирования, интеграции с инструментами сборки и т. д. Кроме того, JUnit 5 более интегрирован с Java 8, особенно с Lambdas и Stream. концепции.

Примеры, использованные в этой статье, можно найти в проекте GitHub .