Вложенные тесты в JUnit 5: как и зачем использовать @Nested
Кратко: Коротко: вложенные тесты (@Nested) позволяют логично сгруппировать связанные сценарии в одном тестовом классе, переиспользовать контекст и улучшить читаемость отчетов. В этой статье показаны пример на классе Customer, рекомендации, чек‑лист и альтернативы.
Что такое вложенный тест?
Аннотация @Nested в JUnit сигнализирует, что помеченный класс — внутренний (inner) класс внутри другого класса. Вложенный тест — это класс с @Nested внутри верхнего тестового класса. Такие классы наследуют контекст родителя и могут находиться внутри верхнего класса или в другом вложенном.
Когда использовать вложенные тесты
- Когда есть логические группы сценариев для одной сущности.
- Когда один метод выполняет несколько обязанностей и нужно тестировать каждую отдельно.
- Чтобы переиспользовать настройки и ресурсы родительского класса.
Важно: @Nested упрощает структуру и отчётность тестов, но не заменяет изоляцию тестов.
Пример: класс Customer

Ниже — пример класса, который мы будем тестировать. Он демонстрирует конструкторы, геттеры/сеттеры и простую бизнес‑логику.
public class Customer {
protected int customerId;
protected String customerName;
protected String customerCode;
// default constructor
public Customer() {
this.customerId = 0;
this.customerName = "";
this.customerCode = "";
}
// primary constructor
public Customer(int customerId, String customerName, String customerCode) {
this.customerId = customerId;
this.customerName = customerName;
this.customerCode = customerCode;
}
// copy constructor
public Customer(Customer customer) {
this.customerId = customer.customerId;
this.customerName = customer.customerName;
this.customerCode = customer.customerCode;
}
// getters and setters
public int getCustomerId() {
return customerId;
}
public void setCustomerId(int customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public String getCustomerCode() {
return customerCode;
}
public void setCustomerCode(String customerCode) {
this.customerCode = customerCode;
}
// determine a customer discount percentage based on customer type
public double customerType(String customerCode) {
double discount = 0;
if (customerCode.toLowerCase().equals("pre")) {
discount = 0.10;
} else if (customerCode.toLowerCase().equals("gen")) {
discount = 0.02;
} else if (customerCode.toLowerCase().equals("new")) {
discount = 0.05;
}
return discount;
}
// determine a customer's grandTotal based on customer type
public double grandTotal(double total) {
double discount = customerType(customerCode);
double discountPercentage = total * discount;
double finalTotal = total - discountPercentage;
return finalTotal;
}
}Создание вложенного теста с JUnit 5
Ниже — тестовый класс, где верхний класс содержит общие данные, а вложенный тест проверяет построение объекта и методы.
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@DisplayName("Customer Test Class Showing How to Create Nested Tests.")
class CustomerTest {
protected int customerId = 301;
protected String customerName = "Mike Wilson";
protected String customerCode = "Pre";
protected double total = 600;
@Nested
@DisplayName("Customer Builder Nested Test Class Within a Top-Level Test Class")
class CustomerBuilderTest {
Customer customer = new Customer(customerId, customerName, customerCode);
double grandTotal = customer.grandTotal(total);
@Test
@DisplayName("Testing the Customer's Class Constructors, Getters and Setters, and Methods.")
void customerBuilder() {
assertAll(() -> {
assertEquals(customerId, customer.getCustomerId());
assertEquals(customerName, customer.getCustomerName());
assertEquals(customerCode, customer.getCustomerCode());
assertEquals(0.10, customer.customerType(customerCode));
assertEquals(540, grandTotal);
});
}
}
}Класс CustomerTest — верхний уровень. CustomerBuilderTest — вложенный класс, который создаёт объект Customer и выполняет проверки через assertAll и assertEquals.
При выполнении тестов вы получите успешный отчёт:
Имена тестов и @DisplayName делают отчёт понятным и человекочитаемым.
Полезные советы и шаблоны
- Используйте @BeforeEach / @AfterEach в верхнем классе для настроек, которыми пользуются вложенные классы.
- Не используйте общий изменяемый state без явной инициализации перед каждым тестом.
- Сохраняйте изоляцию: вложенность — это организационный инструмент, а не способ создавать зависимые тесты.
Шпаргалка по аннотациям JUnit 5
- @Nested — объявляет вложенный тестовый класс.
- @DisplayName — читаемое имя для тестового класса или метода.
- @BeforeEach / @AfterEach — выполняются для каждого теста (также применяются к вложенным).
- @BeforeAll / @AfterAll — выполняются один раз; в контексте вложенных классов их использование ограничено (статический/нестатический контекст).
Когда не стоит использовать вложенные тесты
- Если тесты сильно зависят друг от друга и требуют отдельного lifecycle — лучше разделить по классам.
- В больших проектах с глубокими уровнями вложенности это может ухудшить читабельность.
- Если тесты должны запускаться независимо в разных пайплайнах CI — разместите их в отдельных файлах.
Альтернативы
- Отдельные тестовые классы с общими утилитами и фабриками данных.
- Параметризованные тесты (@ParameterizedTest) для множественных входных данных.
- BDD‑фреймворки для описательных сценариев (например, JGiven, Cucumber).
Критерии приёмки
- Все тесты выполняются локально и в CI без взаимных конфликтов.
- Вложенные тесты читаемы: значения @DisplayName и имена методов понятны.
- Отсутствует неинициализированный общий изменяемый state между тестами.
Чеклист для команды
- Есть верхний тестовый класс с общими настройками.
- Вложенные классы помечены @Nested.
- Использован @DisplayName для читаемости.
- Проверены граничные и нормальные случаи.
- CI отображает структуру вложенных тестов в отчёте.
Простая decision‑flow для выбора вложенности
flowchart TD
A[Нужно ли группировать близкие сценарии?] -->|Да| B[Есть общие настройки/данные?]
A -->|Нет| E[Отдельный тестовый класс]
B -->|Да| C[Использовать @Nested]
B -->|Нет| D[Рассмотреть параметризацию]Резюме
Вложенные тесты в JUnit 5 помогают организовать тесты по смыслу, переиспользовать контекст и улучшить отчётность. Они удобны для группировки сценариев и проверки разных аспектов одного класса, но не заменяют принципы изоляции и детальной архитектуры тестовой базы.