Статьи

Юнит 5 – Основы

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

В этой статье показаны основные концепции JUnit 5, включая жизненный цикл теста, внедрение параметров и утверждения (базовый, время ожидания и исключение).

Документация

Прежде всего, документация JUnit 5 просто великолепна и, на мой взгляд. Он содержит не только исчерпывающую документацию по фреймворку, но и множество примеров, в том числе множество примеров. Не пропустите документацию при изучении JUnit 5: http://junit.org/junit5/docs/current/user-guide/

зависимости

Во-первых, для запуска JUnit 5 требуется Java 8. В заключение. Это дает возможность использовать лямбда-выражения в тестах и ​​сделать их более удобными (лямбда-выражения в основном используются в утверждениях). Во-вторых, JUnit 5 состоит из нескольких артефактов, сгруппированных по JUnit Platform, JUnit Jupiter и JUnit Vintage. Это может звучать страшно, но сегодня с такими инструментами, как Maven или Gradle, это не проблема, и для начала вам нужна отдельная зависимость. Базовая конфигурация Gradle может выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
buildscript {
    ext {
        junitPlatformVersion = '1.0.1'
        junitJupiterVersion = '5.0.1'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.junit.platform:junit-platform-gradle-plugin:${junitPlatformVersion}"
    }
}
 
apply plugin: 'java'
apply plugin: 'org.junit.platform.gradle.plugin'
 
sourceCompatibility = 1.8
 
repositories {
    mavenCentral()
}
 
dependencies {
    testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
}
 
task wrapper(type: Wrapper) {
    gradleVersion = '4.1'
}

JUnit 5 тестовых классов и методов

Общие тестовые аннотации, используемые в тестовом классе (импортированные из org.junit.jupiter.api ):

  • @BeforeAll — выполняется перед всеми методами в тесте
  • @BeforeEach — выполнять перед каждым тестовым методом в тестовом классе
  • @Test — актуальный метод тестирования
  • @AfterEach — выполняется после каждого метода теста в тесте
  • @AfterAll — выполняется после всех методов в тесте

Другие основные, но полезные аннотации:

  • @DisplayName — пользовательское отображаемое имя для тестового класса или метода
  • @Disabled — отключение тестового класса или метода
  • @RepeatedTest — сделать тестовый шаблон из тестового метода
  • @Tag — пометить тестовый класс или метод для дальнейшего выбора теста

Основной пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import org.junit.jupiter.api.*;
 
@DisplayName("JUnit5 - Test basics")
class JUnit5Basics {
 
    @BeforeAll
    static void beforeAll() {
        System.out.println("Before all tests (once)");
    }
 
    @BeforeEach
    void beforeEach() {
        System.out.println("Runs before each test");
    }
 
    @Test
    void standardTest() {
        System.out.println("Test is running");
    }
 
    @DisplayName("My #2 JUnit5 test")
    @Test
    void testWithCustomDisplayName() {
        System.out.println("Test is running");
    }
 
    @DisplayName("Tagged JUnit5 test ")
    @Tag("cool")
    @Test
    void tagged() {
        System.out.println("Test is running");
    }
 
    @Disabled("Failing due to unknown reason")
    @DisplayName("Disabled test")
    @Test
    void disabledTest() {
        System.out.println("Disabled, will not show up");
    }
 
    @DisplayName("Repeated test")
    @RepeatedTest(value = 2, name = "#{currentRepetition} of {totalRepetitions}")
    void repeatedTestWithRepetitionInfo() {
        System.out.println("Repeated test");
    }
 
    @AfterEach
    void afterEach() {
        System.out.println("Runs after each test");
    }
}

Обратите внимание, что тестовые классы и методы не должны быть общедоступными — они могут быть закрытыми .

Жизненный цикл выполнения теста

В JUnit 5 по умолчанию новый экземпляр теста создается для каждого метода теста в классе теста. Это поведение можно настроить с @TestInstance аннотации уровня @TestInstance :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import org.junit.jupiter.api.*;
 
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DisplayName("JUnit5 - Test lifecycle adjustments")
class JUnit5PerClassLifecycle {
 
    private Object first = new Object();
    private Object second;
 
    @BeforeAll
    void beforeAll() {
        this.second = this.first;
        System.out.println("Non static before all.");
    }
 
    @BeforeEach
    void beforeEach() {
        Assertions.assertEquals(first, second);
    }
 
    @Test
    void first() {
        Assertions.assertEquals(first, second);
    }
 
    @Test
    void second() {
        Assertions.assertEquals(first, second);
    }
 
    @AfterAll
    void afterAll() {
        System.out.println("Non static after all.");
    }
 
    @AfterEach
    void afterEach() {
        Assertions.assertEquals(first, second);
    }
}

В режиме PER_CLASS для всех тестов создается один экземпляр теста, и @BeforeAll и @AfterAll больше не должны быть статическими.

Разрешение параметра

Методы тестирования и обратного вызова теперь могут принимать аргументы, такие как org.junit.jupiter.api.TestInfo , org.junit.jupiter.api.RepetitionInfo или org.junit.jupiter.api.TestReporter .

Кроме того, благодаря действительно простому, но мощному API расширения JUnit 5, разрешение пользовательских параметров в методах является вопросом обеспечения собственной реализации org.junit.jupiter.api.extension.ParameterResolver .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class JUnit5BuiltInParameterResolution {
 
    @BeforeAll
    static void beforeAll(TestInfo testInfo) {
        System.out.println("Before all can take parameters. Started: " + testInfo.getDisplayName());
    }
 
    @BeforeAll
    static void beforeAll(TestReporter testReporter) {
        testReporter.publishEntry("myEntry", "myValue");
    }
 
    @BeforeAll
    static void beforeAll(TestInfo testInfo, TestReporter testReporter) {
        testReporter.publishEntry("myOtherEntry", testInfo.getDisplayName());
    }
 
 
    @BeforeEach
    void beforeEach(TestInfo testInfo) {
 
    }
 
    @Test
    void standardTest(TestInfo testInfo) {
 
    }
 
    @DisplayName("Repeated test")
    @RepeatedTest(value = 2, name = "#{currentRepetition} of {totalRepetitions}")
    void repeatedTest(RepetitionInfo repetitionInfo) {
        System.out.println("Repeated test - " + repetitionInfo.toString());
    }
 
    @AfterAll
    static void afterAll() {
 
    }
 
    @AfterAll
    static void afterAll(TestInfo testInfo) {
 
    }
 
    @AfterEach
    void afterEach() {
 
    }
}

Утверждения

JUnit 5 поставляется со многими стандартными утверждениями, которые можно найти в классе org.junit.jupiter.api.Assertions .

Основные утверждения

Основные утверждения: assertEquals , assertArrayEquals , assertSame , assertNotSame , assertTrue , assertFalse , assertNull , assertNotNull , assertLinesMatch , assertIterablesMatch

Пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@Test
void basicAssertions() {
    // arrange
    List<String> owners = Lists.newArrayList("Betty Davis", "Eduardo Rodriquez");
 
    // assert
    assertNotNull(owners);
    assertSame(owners, owners);
    assertFalse(owners::isEmpty); // Lambda expression
    assertEquals(2, owners.size(), "Found owner names size is incorrect");
    assertLinesMatch(newArrayList("Betty Davis", "Eduardo Rodriquez"), owners);
    assertArrayEquals(
        new String[]{"Betty Davis", "Eduardo Rodriquez"},
        owners.toArray(new String[0])
    );
}

Утвердить все

Assertions.assertAll утверждает, что все предоставленные исполняемые файлы не генерируют исключения:

1
2
3
4
Assertions.assertAll(
    () -> Assertions.assertNotNull(null, "May not be null"),
    () -> Assertions.assertTrue(false, "Must be true")
);

Выше будет сообщать о нескольких сбоях:

1
2
3
org.opentest4j.MultipleFailuresError: Multiple Failures (2 failures)
    May not be null ==> expected: not <null>
    Must be true

Примечание: вы можете прочитать об альтернативах в JUnit 4 и AssertJ — http://blog.codeleak.pl/2015/09/assertjs-softassertions-do-we-need-them.html

Утверждения тайм-аута

Утверждения тайм-аута используются для проверки того, что время выполнения задачи не превышено. Существует два варианта подтверждения тайм-аута: assertTimeout и assertTimeoutPreemptively . Оба берут два

  • Выполните задачу синхронно, ожидая ее завершения и затем устанавливая таймауты:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Test
void assertTimeout() {
    // arrange
    Executable task = () -> Thread.sleep(1000);
 
    // waits for the task to finish before failing the test
    Assertions.assertTimeout(Duration.ofMillis(100), task::execute);
}
 
@Test
void assertTimeoutWithThrowingSupplier() {
    // arrange
    ThrowingSupplier<String> task = () -> "result";
 
    // waits for the task to finish before failing the test
    Assertions.assertTimeout(Duration.ofMillis(100), task::get);
}
  • Выполните задачу асинхронно (в новом потоке), прервите выполнение по истечении времени ожидания:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
@Test
void assertTimeoutPreemptively() {
    // arrange
    Executable task = () -> Thread.sleep(1000);
 
    // abort execution when timeout exceeded
    Assertions.assertTimeoutPreemptively(Duration.ofMillis(100), task::execute);
}
 
@Test
void assertTimeoutPreemptivelyWithThrowingSupplier() {
    // arrange
    ThrowingSupplier<String> task = () -> "result";
 
    // abort execution when timeout exceeded, return the result
    String result = Assertions.assertTimeoutPreemptively(Duration.ofMillis(100), task::get);
 
    Assertions.assertEquals("result", result);
}

Исключения

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Test
void assertException() {
    // arrange
    Executable throwingExecutable = () -> {
        throw new RuntimeException("Unexpected error!");
    };
 
    // act and assert
    RuntimeException thrown = Assertions.assertThrows(
        RuntimeException.class, throwingExecutable::execute, "???"
    );
 
    Assertions.assertAll(
        () -> Assertions.assertEquals("Unexpected error!", thrown.getMessage()),
        () -> Assertions.assertNotNull(thrown.getCause())
    );
}

Примечание: вы можете прочитать об альтернативах в JUnit 4 — http://blog.codeleak.pl/2013/07/3-ways-of-handling-exceptions-in-junit.html

Резюме

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

Смотрите также

Смотреть оригинальную статью здесь: JUnit 5 — Основы

Мнения, высказанные участниками Java Code Geeks, являются их собственными.