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 — http://blog.codeleak.pl/2017/06/cleaner-parameterized-tests-with-junit-5.html
Смотреть оригинальную статью здесь: JUnit 5 — Основы
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |