Переход от JUnit4 к новой версии во многом изменил способ расширения функциональных возможностей тестов. Напомню, что в JUnit4 основным механизмом расширения были правила (Rule), которые могли обернуть выполнение теста в дополнительную логическую обработку (например, в реализации абстрактного класса ExternalResource встраивали два дополнительных вызова методов инициализации (который также мог возвращать объект для взаимодействия с создаваемым окружением, например обертку вокруг Android Activity) и финализации (вызывается после выполнения теста и используется для очистки ресурсов). Модель JUnit 5 существенно дополнена и в этой статье мы рассмотрим как можно создавать собственные расширения для JUnit Platform. Начнем с рассмотрения простого примера и для сравнения возьмем пример расширения для JUnit 4. Создадим функцию с одним статическим методом и напишем простой тест для нее: В build.gradle добавим зависимости для junit4 и укажем его как тестовый движок (а также будем использовать hamcrest для описания тестовых утверждений): Теперь сделаем дополнение, которое позволит извлекать тестовые данные и ожидаемые результаты выполнения функции из внешнего файла.  Альтернативно можно использовать базовый абстрактный класс ExternalResource: Теперь можно добавить созданное правило к нашему тесту и получить возможность загружать и использовать тестовые данные из внешнего файла (расположен в src/test/resources): Теперь перейдем к рассмотрению расширений в JUnit Platform и попробуем реализовать эту задачу новым способом. Прежде всего нужно отметить, что новый JUnit Platform предполагает возможность создания разных представлений для описания тестов и возможно как использование синтаксиса JUnit 4 (с установленным JUnit Vintage) или нового синтаксиса JUnit 5, но также возможны совсем другие реализации тестовых движков (например те, которые читают текстовые файлы и запускают запросы к API в соответствии с описанным сценарием). Сейчас мы рассмотрим только модель расширений JUnit5.  Добавим поддержку JUnit5 в gradle: И перепишем тест на использование собственных утверждений junit-jupiter: Расширения в JUnit 5 реализуются с использованием базового интерфейса-маркера Extension и регистрируются через аннотацию @ExtendWith(ExtensionClass.class) с переопределением методов жизненного цикла: before*, after*, где вместо * может использоваться All для однократного выполнения, Each для каждого теста, TestExecution для выполнения теста - интерфейсы Before*Callback и After*Callback; определение условий выполнения теста через переопределение evaluateExecutionCondition - интерфейс ExecuteCondition получение значений для параметризированных тестов через supportsParameter/resolveParameter - в интерфейсе ParameterResolver наблюдение за результатами тестов (testDisabled, testSuccessful, testAborted, testFailed) - в интерфейсе TestWatcher. многократный запуск (или запуск параметрических тестов) - supportsTestTemplate (поддержка запуска в нескольких контекстах) и provideTestTemplateInvocationContexts (поставщик контекстов) - в интерфейсе TestTemplateInvocationContextProvider В нашем случае для реализации загрузки данных до теста можно использовать переопределение beforeAll (для инициализации массива данных) и afterAll (для финализации), но для начала добавим более простую реализацию логирования запуска и завершения тестов.  Для подключения расширения добавим аннотацию @ExtendWith(LogExtension.class) перед определением класса теста (или тестового метода). Теперь попробуем добавить реализацию метода для загрузки данных из внешнего источника: Но здесь возникнет проблема при установке через @ExtendWith, поскольку предполагается передача значения в конструктор и нужно сохранить объект-расширения для доступа к данным. В этом случае можно использовать аннотацию @RegisterExtension перед статическим полем с созданием объекта (аналогично аннотации @Rule для JUnit4): Теперь применим расширение к нашему тестовому классу через аннотацию статического поля: Для обмена данными методы расширения могут использовать store (доступен в ExtensionContext) и взаимодействовать с контекстом запуска тестов (например, получать текущий тестовый класс/метод). Кроме поставки данных через параметры вызова метода также может быть создано расширение для генерации значений как параметров теста: Однако это не решит проблему множественного выполнения теста с различными наборами данных и тест будет выполнен однократно с первой записью из файла. Для поддержки многократного выполнения необходимо обозначить тестовый метод аннотацией @TestTemplate и добавить в расширение реализацию методов интерфейса TestTemplateInvocationContextProvider Для контекста также может быть задано отображаемое имя (например, можно показывать номер итерации), оно будет отображаться в тестовом отчете. Также методы расширения могут взаимодействовать со сгенерированным отчетом через вызовы context.publishReportEntry. После запуска теста отчет может выглядеть подобным образом: Мы рассмотрели основные вопросы создания расширений для JUnit и я надеюсь, что на основе изученных примеров вы сможете реализовать любые сложные сценарии тестирования и подготовки тестовых данных. Статья подготовлена в преддверии старта курса Java QA Engineer. Professional. Также хочу поделиться с вами записью бесплатного вебинара по теме "Пишем тесты с использованием Selenide".  Flutter Developer@YandexPro, DevOps, IT Architect