W3docs

Аннотации JUnit в Java

Основные аннотации JUnit 5 — @Test, @BeforeEach, @AfterEach, @BeforeAll, @AfterAll, @Disabled.

JUnit 5 — это стандартный фреймворк тестирования для Java, и почти всё взаимодействие с ним происходит через аннотации. Вы не пишете метод main и не вызываете методы вручную; вы украшаете обычные методы аннотациями вроде @Test, @BeforeEach и @AfterAll, а движок JUnit обнаруживает их с помощью рефлексии и запускает в нужном порядке. В этой главе рассматриваются основные аннотации жизненного цикла — что означает каждая из них, когда она срабатывает и как они сочетаются, чтобы обеспечить каждому тесту чистое изолированное окружение.

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

Аннотации находятся в org.junit.jupiter.api

API JUnit 5 — это модуль Jupiter. Аннотации, которые вы используете ежедневно, поступают из одного пакета:

АннотацияПрименяется кЗапускается
@Testметододин раз для каждого тестового метода
@BeforeEachметодперед каждым @Test
@AfterEachметодпосле каждого @Test
@BeforeAllstatic-метододин раз, до любого теста в классе
@AfterAllstatic-метододин раз, после всех тестов в классе
@Disabledметод или классникогда (пропускается и фиксируется)
@DisplayNameметод или классзадаёт понятное имя в отчётах

Метод, помеченный @Test, не требует модификатора public в JUnit 5 (package-private допустим) и должен возвращать void.

@Test: единица работы

Тестовый метод проверяет что-либо с помощью статических помощников из org.junit.jupiter.api.Assertions. Если утверждение не выполняется, оно выбрасывает исключение, и движок фиксирует этот тест как провалившийся, не останавливая остальные.

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

    @Test
    void addsTwoNumbers() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3));
    }

    @Test
    void throwsOnDivideByZero() {
        Calculator calc = new Calculator();
        assertThrows(ArithmeticException.class, () -> calc.divide(1, 0));
    }
}

Каждый @Test запускается на новом экземпляре тестового класса — JUnit по умолчанию создаёт новый объект для каждого теста, поэтому поля, установленные в одном тесте, не могут просочиться в другой.

@BeforeEach и @AfterEach: фикстуры для каждого теста

Настройка, необходимая каждому тесту, помещается в метод @BeforeEach; завершение — в @AfterEach. Они обрамляют каждый @Test, давая каждому тесту одинаковую отправную точку.

import org.junit.jupiter.api.*;

class OrderServiceTest {

    private OrderService service;

    @BeforeEach
    void setUp() {
        service = new OrderService(new InMemoryRepo()); // fresh state per test
    }

    @AfterEach
    void tearDown() {
        service.close(); // runs even if the test threw
    }

    @Test
    void placesOrder() {
        assertTrue(service.place("SKU-1", 2));
    }
}

@AfterEach выполняется даже при сбое теста, что делает его подходящим местом для освобождения ресурсов, открытых в @BeforeEach.

@BeforeAll и @AfterAll: один раз на класс

Когда инициализация дорогостоящая и допускает совместное использование — контейнер базы данных, запущенный встроенный сервер — используйте @BeforeAll, чтобы выполнить её один раз, и @AfterAll, чтобы завершить один раз. Поскольку они выполняются до создания каких-либо экземпляров, они должны быть static.

import org.junit.jupiter.api.*;

class RepositoryTest {

    static Database db;

    @BeforeAll
    static void startDatabase() {
        db = Database.start();   // runs once, before everything
    }

    @AfterAll
    static void stopDatabase() {
        db.stop();               // runs once, after everything
    }

    @Test
    void savesRow() {
        assertEquals(1, db.insert("hello"));
    }
}

Полный порядок жизненного цикла для класса с двумя тестами: @BeforeAll → (@BeforeEach@Test@AfterEach) → (@BeforeEach@Test@AfterEach) → @AfterAll. Глава жизненный цикл JUnit подробно рассматривает этот порядок, включая взаимодействие с созданием экземпляров.

@Disabled: пропуск без удаления

@Disabled отключает тест (или целый класс). Движок сообщает о нём как о пропущенном, а не пройденном или провалившемся, поэтому он остаётся видимым. Всегда указывайте причину.

@Test
@Disabled("flaky until the rate-limiter fix lands — see JIRA-1234")
void callsExternalApi() {
    // not executed
}

Рабочий пример: мини-движок тестирования

В этом запускаемом окружении нет jar-файла JUnit, поэтому программа ниже создаёт собственный маленький движок той же формы, что и JUnit. Она объявляет маркерные аннотации (@BeforeAll, @BeforeEach, @Test, @AfterEach, @AfterAll, @Disabled), определяет небольшой аннотированный тестовый класс, а затем использует рефлексию — именно то, что делает движок JUnit изнутри — для обнаружения и запуска методов в порядке жизненного цикла и вывода итогового отчёта пройдено/провалено/пропущено.

java— editable, runs on the server

Что следует извлечь из запуска:

  • @BeforeAll напечатал ровно один раз в самом начале, а @AfterAll — ровно один раз в самом конце: инициализация и завершение на уровне класса обрамляют весь запуск, именно поэтому JUnit требует, чтобы они были static.
  • Каждому выполненному @Test предшествует строка @BeforeEach, а после него следует строка @AfterEach, так что каждый тест запускается на свежеподготовленной фикстуре и сам убирает за собой — перемежающееся обрамление, которое сохраняет независимость тестов.
  • Метод flaky был помечен @Disabled, поэтому напечатал (skipped via @Disabled), и его тело никогда не выполнялось; провальное утверждение внутри него не было достигнуто — в этом и смысл отключения вместо удаления.
  • Цикл обнаружения обрабатывает только методы, для которых isAnnotationPresent(Test.class) возвращает true — аннотации являются лишь метаданными, и именно движок, читающий их с помощью рефлексии, превращает их в поведение, точно так же, как это делает настоящий JUnit.
  • Последняя строка сообщает 2 passed, 0 failed, 1 skipped: два реальных теста прошли, ни один не провалился, а отключённый засчитан как пропущенный, а не проигнорирован молча — такой же учёт пройдено/провалено/пропущено, как в отчёте JUnit.

Практика

Практика
Почему в JUnit 5 метод с аннотацией @BeforeAll должен быть объявлен static?
Почему в JUnit 5 метод с аннотацией @BeforeAll должен быть объявлен static?
Was this page helpful?