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

Что такое кэширование тестового контекста?

2.0 Middle🔥 181 комментариев
#Spring Framework#Тестирование

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

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

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

Кэширование тестового контекста

Кэширование тестового контекста (Test Context Caching) — это оптимизационный механизм в Spring Framework, который повторно использует уже инициализированный ApplicationContext между несколькими тестами вместо создания нового контекста для каждого теста. Это значительно ускоряет выполнение набора тестов, так как инициализация контекста — одна из самых дорогих операций.

Проблема без кэширования

Каждый раз при запуске теста с @SpringBootTest или @WebMvcTest Spring создаёт новый ApplicationContext:

@SpringBootTest
class UserRepositoryTest1 {
    // Создание контекста #1 — 5 секунд
    @Test
    void testFindByName() { }
}

@SpringBootTest
class UserRepositoryTest2 {
    // Создание контекста #2 (тот же!) — 5 секунд
    @Test
    void testFindById() { }
}

// Без кэширования: 5 + 5 = 10 секунд
// С кэшированием: 5 + 0 = 5 секунд (контекст переиспользуется)

Как работает кэширование

Spring кэширует контексты на основе конфигурации. Если две конфигурации идентичны, они будут использовать одинаковый кэшированный контекст.

@SpringBootTest
class Test1 {
    @Test
    void test() {  }
}  // Контекст создан и добавлен в кэш

@SpringBootTest
class Test2 {
    @Test
    void test() {  }
}  // Контекст найден в кэше и переиспользуется!

@SpringBootTest(properties = {"app.name=test"})  // ❌ Другая конфигурация
class Test3 {
    @Test
    void test() {  }
}  // Контекст создан НОВЫЙ (конфигурация отличается)

Ключи кэширования

Spring генерирует ключ кэша на основе:

1. Основной класс конфигурации (@SpringBootTest указывает classes)
2. Active profiles (spring.profiles.active)
3. Context initializers
4. Test properties
5. Environment properties
6. Custom context factories

Пример использования

@SpringBootTest
class UserServiceTest {
    @Autowired
    private UserService userService;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void testCreateUser() {
        // Контекст уже инициализирован и кэширован
        User user = userService.createUser("John");
        assertNotNull(user);
    }
}

@SpringBootTest
class OrderServiceTest {
    @Autowired
    private OrderService orderService;
    
    @Test
    void testCreateOrder() {
        // Переиспользуется ТОТ ЖЕ контекст из первого теста
        // Потому что конфигурация идентична
        Order order = orderService.createOrder();
        assertNotNull(order);
    }
}

Когда контексты НЕ переиспользуются

// Контекст #1
@SpringBootTest
class Test1 {
    @Test
    void test() { }
}

// Контекст #2 (из-за @WebMvcTest)
@WebMvcTest(UserController.class)
class Test2 {
    @Test
    void test() { }
}  // Другой тип контекста!

// Контекст #3 (из-за properties)
@SpringBootTest(properties = {"app.mode=production"})
class Test3 {
    @Test
    void test() { }
}  // Конфигурация отличается!

// Контекст #4 (из-за activeProfiles)
@SpringBootTest
@ActiveProfiles("test")
class Test4 {
    @Test
    void test() { }
}  // Профиль отличается!

Проблема: грязные данные

Кэширование может привести к проблеме грязных данных в БД, если тесты не очищают состояние:

@SpringBootTest
class Test1 {
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void testCreateUser() {
        userRepository.save(new User("John"));  // Данные сохранены
        // НЕ очищаем данные!
    }
}

@SpringBootTest  // Контекст переиспользуется!
class Test2 {
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void testGetUser() {
        // ❌ ПРОБЛЕМА: данные из Test1 всё ещё в БД!
        List<User> users = userRepository.findAll();
        assertEquals(1, users.size());  // ❌ Ожидаем 1, но может быть больше
    }
}

Решение: Транзакции и @Transactional

@SpringBootTest
class UserRepositoryTest {
    @Autowired
    private UserRepository userRepository;
    
    @Test
    @Transactional  // ✅ Автоматически откатывает изменения после теста
    void testCreateUser() {
        User user = userRepository.save(new User("John"));
        assertNotNull(user.getId());
        // Транзакция откатывается автоматически
    }
    
    @Test
    @Transactional
    void testFindUser() {
        // Дата всегда чистая благодаря @Transactional
        assertTrue(userRepository.findAll().isEmpty());
    }
}

Решение: @DirtiesContext

Если нужно инвалидировать кэш после теста:

@SpringBootTest
class Test1 {
    @Autowired
    private ConfigService configService;
    
    @Test
    @DirtiesContext  // ✅ После этого теста контекст закэшируется заново
    void testChangeConfig() {
        configService.setDebug(true);
        assertTrue(configService.isDebug());
    }
}

@SpringBootTest
class Test2 {
    @Autowired
    private ConfigService configService;
    
    @Test
    void testDebugDisabled() {
        // ✅ Свежий контекст благодаря @DirtiesContext в Test1
        assertFalse(configService.isDebug());
    }
}

Встроенная БД для тестов

@SpringBootTest
class IntegrationTest {
    @Autowired
    private UserRepository userRepository;
    
    @BeforeEach  // ❌ Выполняется ДО каждого теста (медленно)
    void setup() {
        // Инициализация данных
    }
    
    @Test
    void testCreateUser() { }
}

// ✅ Лучше: используй @Transactional для откатов
@SpringBootTest
class IntegrationTestOptimized {
    @Autowired
    private UserRepository userRepository;
    
    @Test
    @Transactional  // Откатывает БД после теста
    void testCreateUser() { }
    
    @Test
    @Transactional
    void testDeleteUser() { }
}

Логирование кэширования

Для отладки можно включить логирование:

# application-test.yml
logging:
  level:
    org.springframework.test.context.cache: DEBUG

Вывод:

[TestContextManager] Caching context [...] under key [DefaultTestContext@...]
[TestContextManager] Retrieved context from cache [DefaultTestContext@...]
[TestContextManager] Storing application context in the test context cache

Рекомендации по использованию

  1. Держи тесты независимыми — не полагайся на порядок выполнения
  2. Используй @Transactional для чистоты данных БД
  3. Используй @DirtiesContext только если необходимо инвалидировать контекст
  4. Избегай @DirtiesContext в часто используемых тестах — замедляет выполнение
  5. Группируй тесты — тесты с одинаковой конфигурацией будут переиспользовать контекст

Оптимизация тестов

// ❌ Плохо: каждый тест создаёт новый контекст
@SpringBootTest(classes = {AppConfig1.class})
class Test1 { }

@SpringBootTest(classes = {AppConfig2.class})
class Test2 { }

// ✅ Хорошо: оба теста используют одинаковый контекст
@SpringBootTest
class Test3 { }

@SpringBootTest
class Test4 { }

// ✅ Очень хорошо: используй слайсы для меньших контекстов
@WebMvcTest(UserController.class)
class ControllerTest { }

@DataJpaTest
class RepositoryTest { }

Кэширование тестового контекста — это критичный механизм для ускорения набора тестов, но требует понимания и аккуратности в использовании, особенно когда речь идёт о состоянии БД между тестами.

Что такое кэширование тестового контекста? | PrepBro