Before/After аннотации в JUnit — руководство

JUnit предоставляет четыре основных аннотации для выполнения вспомогательных действий вокруг тестов: @BeforeAll и @AfterAll выполняются один раз до/после всех тестов, @BeforeEach и @AfterEach — перед и после каждого теста. Эти аннотации помогают устанавливать и освобождать ресурсы, делать логирование и готовить окружение. Если нужен не static @BeforeAll, используйте @TestInstance(Lifecycle.PER_CLASS).
О чём этот материал
В этой статье объяснены назначение и правила использования аннотаций JUnit: @BeforeAll, @BeforeEach, @AfterAll и @AfterEach. Приведены рабочие примеры на основе простого класса Calculator, перечислены типичные сценарии применения, альтернативы, частые ошибки и чек-листы для разработчиков и тестировщиков.
Важно: термины
- Аннотация — метка в коде, которую тестовый фреймворк обрабатывает перед исполнением метода.
- Test instance — экземпляр класса теста, который создаёт фреймворк для выполнения тестов.
Зачем нужны before/after-аннотации
Тесты часто требуют подготовки и очистки: открытие соединений, подготовка файлов, очистка временных данных, сброс состояния стаба. Помещать такую логику внутрь тестов неудобно и приводит к дублированию. Аннотации before/after позволяют централизовать подготовку и освобождение ресурсов.
Короткий список типичных задач:
- подключение и отключение к базе данных;
- создание/удаление временных файлов;
- инициализация общих тестовых данных;
- логирование и сбор метрик;
- сброс состояния одиночных сервисов/ста́бов.
Правила и обязательные требования
- @BeforeAll и @AfterAll в JUnit Jupiter (JUnit 5) по умолчанию должны быть static.
- Методы с этими аннотациями должны возвращать void и быть доступными (не private).
- @BeforeEach и @AfterEach — нестатические методы, выполняются для каждого экземпляра тестового класса.
- Последовательность выполнения: @BeforeAll → (@BeforeEach → @Test → @AfterEach)* → @AfterAll.
Пример: класс Calculator
Класс, над которым показаны тесты, прост и служит для иллюстрации:
package com.app;
public class Calculator {
public static int add(int num1, int num2) {
return num1 + num2;
}
public static int subtract(int num1, int num2) {
return num1 - num2;
}
public static int multiply(int num1, int num2) {
return num1 * num2;
}
public static int divide(int num1, int num2) {
return num1 / num2;
}
}Пример тест-класса с @BeforeAll
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
@DisplayName("Test class demonstrating how to use the before and after annotations.")
class CalculatorTest {
@BeforeAll
public static void powerOnCalculator() {
System.out.println("The calculator is on");
}
@Test
@DisplayName("Testing method that adds two integer values.")
public void testAdd() {
assertEquals(7, Calculator.add(3, 4));
}
@Test
@DisplayName("Testing method that subtracts one integer value from another.")
public void testSubtract() {
assertEquals(6, Calculator.subtract(9, 3));
}
@Test
@DisplayName("Testing method that multiplies two integer values")
public void testMultiply() {
assertEquals(10, Calculator.multiply(5, 2));
}
@Test
@DisplayName("Testing method that divides one integer value by another")
public void testDivide() {
assertEquals(2, Calculator.divide(4, 2));
}
}Если powerOnCalculator() содержит ошибку — все тесты пометятся как не выполненные/проваленные, потому что подготовка не удалась.

@BeforeEach — подготовка перед каждым тестом
@BeforeEach выполняется перед каждым тестовым методом. Это удобно, если нужно гарантировать чистое состояние между тестами.
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@DisplayName("Test class demonstrating how to use the before and after annotations.")
class CalculatorTest {
@BeforeAll
public static void powerOnCalculator() {
System.out.println("The calculator is on");
}
@BeforeEach
public void clearCalculator() {
System.out.println("The calculator is ready");
}
// ... тестовые методы как выше ...
}
Обратите внимание: @BeforeEach не static, выполняется столько раз, сколько тестовых методов.
@AfterAll — очистка после всех тестов
@AfterAll запускается после завершения всех тестов в классе. Используется для закрытия соединений, удаления файлов и других финализаций.
@AfterAll
public static void powerOffCalculator() {
System.out.println("The calculator is off");
}
@AfterEach — очистка после каждого теста
@AfterEach выполняется после каждого тестового метода. Полезно для сброса состояния, удаления временных файлов и других задач очистки.
@AfterEach
public void returnResults() {
System.out.println("The results are ready");
}
Очередность в выводе: сначала @BeforeEach, затем тест, затем @AfterEach.
Частые ошибки и как их избежать
- Сделать @BeforeAll нестатическим без дополнительных настроек. Решение: либо сделать метод static, либо использовать @TestInstance(Lifecycle.PER_CLASS).
- Пытаться выполнять долгие операции в @BeforeEach без надобности — это замедлит весь набор тестов. Переносите тяжёлую инициализацию в @BeforeAll, если состояние может быть общим.
- Прятать исключения в @BeforeAll/@BeforeEach — лучше позволять тестовому фреймворку показать причину ошибки.
- Использовать общие статические поля без надлежащего сброса между тестами — это приводит к нежелательной зависимости тестов друг от друга.
Альтернативные подходы и расширенные варианты
- @TestInstance(Lifecycle.PER_CLASS)
Если вы не хотите делать @BeforeAll static, можно настроить JUnit создавать один экземпляр тестового класса для всех тестов:
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
@TestInstance(Lifecycle.PER_CLASS)
class CalculatorTest {
@BeforeAll
void powerOnCalculator() {
// не static
}
// ...
}Использовать фикстуры уровня Suite или глобальные расширения (Extension) в JUnit для подготовки контекста один раз для нескольких классов тестов.
Внешняя инфраструктура: контейнерные тесты (Testcontainers) для изоляции БД/зависимостей.
Когда аннотации не подходят
- Если подготовка занимает очень большое время, и вы запускаете подмножество тестов параллельно — возможно лучше изолировать инициализацию в отдельных инфраструктурных скриптах.
- Если тесты требуют разного глобального состояния, которое нельзя легко менять между тестами — стоит пересмотреть дизайн тестов.
Совместимость и переход с JUnit 4 на JUnit 5
В JUnit 4 эквивалентами выступали @BeforeClass, @Before, @AfterClass, @After. JUnit 5 (Jupiter) ввёл те же концепции, но с такими отличиями, как возможность менять поведение через @TestInstance и расширять поведение через Extension API.
Мини-методология для написания «правильного» набора тестов
- Определите ресурсы, которые можно разделять между тестами (БД, конфигурация) и которые требуют пер-теста очистки.
- Перенесите тяжёлую общую инициализацию в @BeforeAll; легкую и быструю — в @BeforeEach.
- Всегда освобождайте ресурсы в @AfterEach/@AfterAll соответственно.
- Не скрывайте исключения — пусть ТСР покажет причину отказа.
- Добавьте логирование в before/after, чтобы было видно, что именно и когда выполняется.
Чек-лист для разработчика и тестировщика
- @BeforeAll: static или используется @TestInstance(PER_CLASS).
- @BeforeEach: нет долгих блокирующих операций.
- @AfterEach: все временные данные удаляются.
- @AfterAll: корректно закрываются соединения и файлы.
- Нет общего mutable состояния между тестами без сброса.
- Лог сообщений в before/after понятен и полезен.
Критерии приёмки
- Все тесты проходят локально и в CI в изолированном окружении.
- Подготовка и очистка выполняется корректно для каждого теста.
- Нет утечек ресурсов (сокетов, файлов, соединений).
- Тесты запускаются параллельно, если это требуется, и сохраняют корректность.
Краткий словарь
- @BeforeAll: метод, выполняемый один раз перед всеми тестами класса.
- @BeforeEach: метод, выполняемый перед каждым тестовым методом.
- @AfterEach: метод, выполняемый после каждого тестового метода.
- @AfterAll: метод, выполняемый один раз после всех тестов класса.
Примеры перехода и шаблоны
Шаблон для классов с дорогой инициализацией:
@TestInstance(Lifecycle.PER_CLASS)
class ExpensiveSetupTest {
@BeforeAll
void globalSetup() {
// создание контейнера или долгой инфраструктуры
}
@BeforeEach
void setupPerTest() {
// подготовка, быстрые операции
}
@AfterEach
void teardownPerTest() {
// чистка после теста
}
@AfterAll
void globalTeardown() {
// остановка контейнеров, удаление временных артефактов
}
}Заключение
Аннотации @BeforeAll, @BeforeEach, @AfterEach и @AfterAll упрощают поддержку и читаемость тестов. Они помогают централизовать подготовку и очистку, уменьшают дублирование кода и повышают надёжность набора тестов. Выбирайте static или per-class поведение осознанно, документируйте намерения и добавляйте простую проверку в CI, чтобы избежать скрытых зависимостей между тестами.
Ключевые рекомендации:
- Используйте @BeforeAll для дорогой общей инициализации.
- Используйте @BeforeEach для гарантий чистоты состояния между тестами.
- Освобождайте ресурсы в @AfterEach / @AfterAll.
- Рассмотрите @TestInstance(Lifecycle.PER_CLASS), если не хотите static методы.
Похожие материалы
Конвертация MP4 в MP3 — способы и советы
Как настроить сетевой домен — полное руководство
Как добавить рамку к фото — инструменты и методы
Как подключить Fitbit к iPhone — полное руководство
Проверка совместимости игр Steam с Steam Deck