← Назад к вопросам

Что такое Extension в JUnit?

2.0 Middle🔥 141 комментариев
#Основы Java#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Extension в JUnit 5

Extension — это механизм в JUnit 5 для расширения функциональности тестового фреймворка. Это замена для JUnit 4 Rules и Runners, но значительно более мощная и гибкая.

Зачем нужны Extension

Extension позволяют:

  • Инициализировать и очищать ресурсы
  • Внедрять зависимости в тесты
  • Регистрировать callback для событий жизненного цикла
  • Параметризировать тесты
  • Условно отключать/включать тесты
  • Обрабатывать результаты тестов

Основные интерфейсы Extension

1. BeforeEachCallback / AfterEachCallback

public class DatabaseExtension implements 
        BeforeEachCallback, 
        AfterEachCallback {
    
    private Connection connection;
    
    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        // Выполняется перед каждым тестом
        connection = DriverManager.getConnection(
            "jdbc:h2:mem:test"
        );
    }
    
    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        // Выполняется после каждого теста
        if (connection != null && !connection.isClosed()) {
            connection.close();
        }
    }
}

// Использование
@ExtendWith(DatabaseExtension.class)
class UserRepositoryTest {
    
    @Test
    void shouldFindUser() {
        // connection готова к использованию
    }
}

2. BeforeAllCallback / AfterAllCallback

public class ServerExtension implements 
        BeforeAllCallback, 
        AfterAllCallback {
    
    private static TestServer server;
    
    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        // Выполняется один раз перед всеми тестами в классе
        server = new TestServer();
        server.start();
    }
    
    @Override
    public void afterAll(ExtensionContext context) throws Exception {
        // Выполняется один раз после всех тестов
        if (server != null) {
            server.stop();
        }
    }
}

3. ParameterResolver

Внедряет зависимости в параметры теста:

public class RandomIntegerResolver implements ParameterResolver {
    
    @Override
    public boolean supportsParameter(
            ParameterContext parameterContext,
            ExtensionContext extensionContext) {
        return parameterContext.getParameter()
            .getType() == Integer.class &&
            parameterContext.isAnnotated(Random.class);
    }
    
    @Override
    public Object resolveParameter(
            ParameterContext parameterContext,
            ExtensionContext extensionContext) {
        return new java.util.Random().nextInt(100);
    }
}

// Custom аннотация
@Retention(RetentionPolicy.RUNTIME)
public @interface Random {}

// Использование
@ExtendWith(RandomIntegerResolver.class)
class RandomNumberTest {
    
    @Test
    void shouldGenerateRandomNumber(@Random Integer number) {
        assertThat(number).isBetween(0, 99);
    }
}

4. TestInstancePostProcessor

Обрабатывает экземпляр теста после создания:

public class InjectDependencyExtension 
        implements TestInstancePostProcessor {
    
    @Override
    public void postProcessTestInstance(
            Object testInstance,
            ExtensionContext context) throws Exception {
        // Внедрение mock объектов
        Field[] fields = testInstance.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(InjectMock.class)) {
                field.setAccessible(true);
                field.set(testInstance, Mockito.mock(field.getType()));
            }
        }
    }
}

5. ExecutionCondition

Условно отключает/включает тесты:

public class WindowsOnlyExtension implements ExecutionCondition {
    
    @Override
    public ConditionEvaluationResult evaluateExecutionCondition(
            ExtensionContext context) {
        String osName = System.getProperty("os.name");
        
        if (osName.contains("Windows")) {
            return ConditionEvaluationResult.enabled(
                "Test runs on Windows"
            );
        }
        
        return ConditionEvaluationResult.disabled(
            "Test runs only on Windows"
        );
    }
}

@ExtendWith(WindowsOnlyExtension.class)
class WindowsSpecificTest {
    
    @Test
    void shouldWorkOnWindows() {
        // Выполнится только на Windows
    }
}

Встроенные Extension в JUnit 5

@TempDir — для временных файлов:

class FileOperationTest {
    
    @Test
    void shouldCreateFile(@TempDir Path tempDir) throws IOException {
        Path file = tempDir.resolve("test.txt");
        Files.write(file, "content".getBytes());
        
        assertThat(file).exists();
    }
}

@RegisterExtension — программная регистрация:

class ExtensionRegistrationTest {
    
    @RegisterExtension
    static DatabaseExtension dbExtension = new DatabaseExtension();
    
    @Test
    void shouldAccessDatabase() {
        // dbExtension инициализирован
    }
}

Практический пример: Custom Extension

public class LoggingExtension implements 
        BeforeEachCallback, 
        AfterEachCallback,
        TestExecutionExceptionHandler {
    
    @Override
    public void beforeEach(ExtensionContext context) {
        String testName = context.getDisplayName();
        System.out.println("[BEFORE] " + testName);
    }
    
    @Override
    public void afterEach(ExtensionContext context) {
        String testName = context.getDisplayName();
        System.out.println("[AFTER] " + testName);
    }
    
    @Override
    public void handleTestExecutionException(
            ExtensionContext context,
            Throwable throwable) throws Throwable {
        System.err.println("[ERROR] " + context.getDisplayName());
        throw throwable;  // перебросить исключение
    }
}

@ExtendWith(LoggingExtension.class)
class UserServiceTest {
    
    @Test
    void shouldCreateUser() {
        // Логирование автоматически
    }
}

Extension vs Rules в JUnit 4

JUnit 4 (Rules):

class OldTest {
    @Rule
    public TestRule rule = new TestRule() {
        // ограниченный функционал
    };
}

JUnit 5 (Extension):

@ExtendWith(MyExtension.class)
class NewTest {
    // намного более мощный механизм
}

Best Practices

1. Создавай специализированные Extension

// ✅ Хорошо — специфичный Extension
public class PostgresTestExtension implements BeforeAllCallback {
    // специфичная реализация
}

// ❌ Плохо — общий Extension на все случаи
public class GenericExtension implements 
        BeforeAllCallback,
        BeforeEachCallback,
        AfterEachCallback,
        TestExecutionExceptionHandler {
    // слишком много ответственности
}

2. Используй CompositeExtension для комбинации

public class TestContainerExtension implements BeforeAllCallback {
    // Extension, использующий другие Extension
}

3. Документируй Extension поведение

/**
 * Extension для управления PostgreSQL Docker контейнером.
 * 
 * Создаёт контейнер перед тестами и удаляет после.
 * Предоставляет connection string через ExtensionContext.
 */
public class PostgresExtension implements BeforeAllCallback {
    // ...
}

Extension — это современный и мощный способ расширения JUnit 5 функциональности, позволяющий создавать чистые и переиспользуемые тестовые инструменты.