Использование @BeforeAll, @BeforeEach, @AfterAll и @AfterEach в JUnit
JUnit предоставляет четыре ключевые аннотации для управления жизненным циклом тестов: @BeforeAll и @AfterAll выполняются один раз на класс (обычно статические методы), а @BeforeEach и @AfterEach — перед и после каждого теста соответственно. Эти фикстуры помогают настраивать окружение (подключение к БД, подготовка файлов) и освобождать ресурсы; есть нюансы со статикой, параллельным запуском и TestInstance.

JUnit часто применяется для модульного тестирования Java-приложений. В дополнение к самих тестам часто нужны вспомогательные действия: создание соединений, подготовка ресурсов, очистка после теста. Для этого JUnit предоставляет пару «до» и «после» аннотаций, которые позволяют централизованно организовать подготовку и освобождение ресурсов.
Что делает каждая аннотация
- @BeforeAll — выполнить один раз до запуска всех тестов класса. Подходит для дорогих операций: подключение к БД, инициализация общей структуры.
- @AfterAll — выполнить один раз после завершения всех тестов класса. Подходит для закрытия соединений и очистки глобальных ресурсов.
- @BeforeEach — выполнить перед каждым тестовым методом. Используется для подготовки чистого окружения для каждого теста.
- @AfterEach — выполнить после каждого тестового метода. Используется для отката изменений и освобождения локальных ресурсов.
Каждая из аннотаций имеет обязательные требования к сигнатуре методов в стандартном поведении JUnit Jupiter (по умолчанию):
- @BeforeAll и @AfterAll: метод должен быть public static void (если не используется @TestInstance).
- @BeforeEach и @AfterEach: метод должен быть public void и не static.
Ниже — подробные примеры на простом классе калькулятора.
Пример: 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
Метод с аннотацией @BeforeAll выполняется один раз перед всеми тестами. В классическом варианте он должен быть static.
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("Класс тестов, демонстрирующий before/after аннотации")
class CalculatorTest {
@BeforeAll
public static void powerOnCalculator() {
System.out.println("The calculator is on");
}
@Test
@DisplayName("Тест сложения двух целых чисел")
public void testAdd() {
assertEquals(7, Calculator.add(3, 4));
}
@Test
@DisplayName("Тест вычитания одного целого из другого")
public void testSubtract() {
assertEquals(6, Calculator.subtract(9, 3));
}
@Test
@DisplayName("Тест умножения двух целых чисел")
public void testMultiply() {
assertEquals(10, Calculator.multiply(5, 2));
}
@Test
@DisplayName("Тест деления одного целого на другой")
public void testDivide() {
assertEquals(2, Calculator.divide(4, 2));
}
}В данном примере powerOnCalculator() печатает строку один раз перед запуском всех тестов.
Важно: метод, помеченный @BeforeAll, сам по себе не отображается как тест в отчёте. Если в нём возникнет исключение, оно прервет выполнение тестов и пометит запуск как провал.
Пример использования @BeforeEach
@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;
import org.junit.jupiter.api.DisplayName;
@DisplayName("Класс тестов, демонстрирующий before/after аннотации")
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");
}
@Test
@DisplayName("Тест сложения двух целых чисел")
public void testAdd() {
assertEquals(7, Calculator.add(3, 4));
}
@Test
@DisplayName("Тест вычитания одного целого из другого")
public void testSubtract() {
assertEquals(6, Calculator.subtract(9, 3));
}
@Test
@DisplayName("Тест умножения двух целых чисел")
public void testMultiply() {
assertEquals(10, Calculator.multiply(5, 2));
}
@Test
@DisplayName("Тест деления одного целого на другой")
public void testDivide() {
assertEquals(2, Calculator.divide(4, 2));
}
}Вывод покажет строку “The calculator is ready” перед каждым тестом.
Обратите внимание: @BeforeEach не статичен и выполняется после @BeforeAll.
Пример использования @AfterAll
@AfterAll выполняется один раз в конце выполнения всех тестов класса. Обычно используется для освобождения глобальных ресурсов.
import org.junit.jupiter.api.AfterAll;
@AfterAll
public static void powerOffCalculator() {
System.out.println("The calculator is off");
}Размещение такого метода в тестовом классе приведёт к выводу сообщения в конце всех запусков.
Пример использования @AfterEach
@AfterEach выполняется после каждого тестового метода, что удобно для очистки состояния, удаления временных файлов или отката изменений.
import org.junit.jupiter.api.AfterEach;
@AfterEach
public void returnResults() {
System.out.println("The results are ready");
}При четырёх тестах метод с @AfterEach выполнится четыре раза — по одному разу после каждого теста.
Важные замечания и частые ошибки
- Статические требования. По умолчанию @BeforeAll и @AfterAll должны быть static. Если вам нужно не static поведение, используйте @TestInstance(TestInstance.Lifecycle.PER_CLASS), тогда методы могут быть нестатичными.
- Параллельный запуск тестов. При параллельном выполнении тестовых методов глобальные ресурсы, установленные в @BeforeAll, должны быть потокобезопасны. Иначе возможны гонки и флаки.
- Исключения в фикстурах. Исключение в @BeforeAll или @BeforeEach обычно прерывает соответствующие тесты. Обрабатывайте исключения внутри фикстур, если хотите продолжить выполнение.
- Порядок выполнения. @BeforeAll → @BeforeEach → тест → @AfterEach → @AfterAll.
- Взаимодействие с менеджерами ресурсов. Для интеграционных тестов лучше использовать тестовые контейнеры и managed resources (например, Testcontainers), а не ручные connect/disconnect в фикстурах.
Important: если вам нужно подготовить один и тот же изменяемый объект для каждого теста, создавайте его в @BeforeEach, чтобы тесты оставались независимыми.
Альтернативные подходы
- @TestInstance(TestInstance.Lifecycle.PER_CLASS) — позволяет использовать нестатические @BeforeAll/@AfterAll и хранить состояние класса между тестами.
- JUnit Extensions — реализуйте Extension для повторно используемой логики подготовки/очистки (например, для установки окружения, временных файлов, мок-серверов).
- Testcontainers и другие фреймворки — для управления временными БД и сервисами вместо ручной инициализации.
Пошаговая мини-методология подготовки фикстур
- Определите уровень ресурса: глобальный для класса или локальный для теста.
- Если ресурс общий и дорогой — используйте @BeforeAll/@AfterAll.
- Если ресурс должен быть «чистым» для каждого теста — используйте @BeforeEach/@AfterEach.
- Обработайте исключения и сделайте операции идемпотентными, чтобы пропуск/повторение не ломали тестовую среду.
- Добавьте логирование и, при необходимости, артефакты для диагностики провалов.
Ментальные модели и эвристики
- «Подготовка — Тест — Очистка»: каждая проверка должна иметь атомарное начало и конец.
- «Дорогие вещи один раз, дешёвые — каждый тест»: подключение к БД один раз, очистка таблиц перед каждым тестом.
- «Тесты должны быть изолированы»: избегайте общих изменяемых состоятелй между тестами.
Когда это может не сработать
- Если тесты запускаются параллельно и фикстуры не потокобезопасны — возможны непредсказуемые результаты.
- Когда фикстура выполняет тяжёлые операции и делает тесты медленными — возможно лучше запускать отдельный набор интеграционных тестов.
- Если настройка требует контекста, которого нет в unit-тестах (например, контейнеры ОС), лучше использовать интеграционные тесты.
Рекомендации по безопасности и ресурсам
- Всегда закрывайте внешние соединения в @AfterAll или в try-with-resources внутри фикстуры.
- Ограничьте влияние глобальных статических состояний — используйте их только если это оправдано.
Контроль качества: чек-листы по ролям
Разработчик:
- Убедиться, что @BeforeEach и @AfterEach делают тесты детерминированными.
- Не хранить состояние между тестами без явной причины.
- Логировать ошибки в фикстурах для последующего анализа.
QA-инженер:
- Проверить, что исключения в @BeforeAll помечают всю сессию как провал.
- Запустить тесты в режиме параллельного выполнения и проверить на гонки.
DevOps/релизный инженер:
- Оценить время выполнения фикстур и их влияние на CI.
- Рассмотреть разделение unit- и integration-тестов в разные пайплайны.
Критерии приёмки
- Тесты выполняются без зависимостей друг от друга.
- @BeforeAll и @AfterAll корректно инициализируют и освобождают общие ресурсы.
- При параллельном выполнении тестов отсутствуют гонки на общих ресурсах.
Короткий глоссарий
- JUnit — фреймворк для модульного тестирования Java.
- Тестовый метод — метод с аннотацией @Test, который выполняет проверку.
- Фикстура — подготовка и очистка окружения теста (методы с before/after аннотациями).
- @TestInstance — аннотация, управляющая временем жизни экземпляра тестового класса.
Дерево принятия решения (Mermaid)
flowchart TD
A[Нужно настроить ресурс для тестов?] -->|Да| B{Ресурс общий для всего набора тестов?}
A -->|Нет| Z[Ничего не делать]
B -->|Да| C{Нужен один экземпляр на класс и позволяет static?}
B -->|Нет| D[Использовать @BeforeEach/@AfterEach]
C -->|Да| E[Использовать @BeforeAll/@AfterAll 'static']
C -->|Нет| F[Использовать @TestInstance'PER_CLASS' и нестатические @BeforeAll/@AfterAll]Примеры отказа и обходные пути
Проблема: @BeforeAll static не подходит, потому что вы хотите доступ к нестатическим полям. Решение: добавить @TestInstance(TestInstance.Lifecycle.PER_CLASS) к тестовому классу.
Проблема: тесты флаки при параллельном запуске. Решение: сделать глобальные ресурсы потокобезопасными или перейти к пер-тестовым ресурсам.
Лучшие практики
- Минимизируйте работу в @BeforeAll до действительно необходимого.
- Используйте @AfterEach для гарантированного отката побочных эффектов.
- Логируйте начало и конец фикстур для быстрой диагностики.
- По возможности избегайте глобального изменяемого состояния.
Краткое резюме
JUnit-аннотации @BeforeAll, @BeforeEach, @AfterAll и @AfterEach помогают структурировать подготовку и очистку окружения для тестов. Выбор между ними зависит от того, насколько ресурс общ и дорог в инициализации, а также от требований к изоляции и потокобезопасности. При необходимости используйте @TestInstance или расширения JUnit для более гибкой настройки.
Summary:
- Применяйте @BeforeAll/@AfterAll для глобальной подготовки и очистки.
- Применяйте @BeforeEach/@AfterEach для локальной подготовки и отката.
- Контролируйте побочные эффекты и учитывайте параллельный запуск.
Похожие материалы
Учите язык с Toucan, не теряя продуктивности
Как попросить прибавку: 6 советов и шаблоны
Буфер обмена подменяет адрес кошелька — удаление вредоноса
Заклинания Гарри Поттера в iPhone
Решение проблем Netflix быстро