Что такое кэширование тестового контекста?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Кэширование тестового контекста
Кэширование тестового контекста (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
Рекомендации по использованию
- Держи тесты независимыми — не полагайся на порядок выполнения
- Используй @Transactional для чистоты данных БД
- Используй @DirtiesContext только если необходимо инвалидировать контекст
- Избегай @DirtiesContext в часто используемых тестах — замедляет выполнение
- Группируй тесты — тесты с одинаковой конфигурацией будут переиспользовать контекст
Оптимизация тестов
// ❌ Плохо: каждый тест создаёт новый контекст
@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 { }
Кэширование тестового контекста — это критичный механизм для ускорения набора тестов, но требует понимания и аккуратности в использовании, особенно когда речь идёт о состоянии БД между тестами.