Гид по технологиям

Вложенные тесты в JUnit 5: как и зачем использовать @Nested

3 min read Тестирование Обновлено 28 Dec 2025
JUnit 5 @Nested: вложенные тесты
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 помогают организовать тесты по смыслу, переиспользовать контекст и улучшить отчётность. Они удобны для группировки сценариев и проверки разных аспектов одного класса, но не заменяют принципы изоляции и детальной архитектуры тестовой базы.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Отключить подсказки Siri на iPhone
iOS

Отключить подсказки Siri на iPhone

Исключения Windows Defender в Windows 10
Windows

Исключения Windows Defender в Windows 10

Поделиться паролем Wi‑Fi на Android — QR и Nearby
Android.

Поделиться паролем Wi‑Fi на Android — QR и Nearby

Как исправить ошибку Windows 0x8007045d
Windows

Как исправить ошибку Windows 0x8007045d

Как удалить пароль из PDF на Windows и macOS
Инструкции

Как удалить пароль из PDF на Windows и macOS

Исправление ошибки 0x80004005 в Windows
Windows

Исправление ошибки 0x80004005 в Windows