DEV Community на русском
Подписаться
Что могут упустить Spring Data тесты: Тестирование за пределами контекста персистентности Hibernate (Глава 7)
Каждый EntityManager поддерживает кэш первого уровня, карту идентификаторов сущностей в текущем контексте персистентности. Когда вызывается find() для уже управляемой сущности, Hibernate возвращает существующий объект без запроса к базе данных, что является стандартным поведением кэша L1. Однако стандартные тесты Spring Data с использованием @DataJpaTest могут ошибочно проверять состояние этого кэша L1 вместо фактического сохранения в базе данных. Это может скрывать критические проблемы, такие как нарушения ограничений или ошибки сопоставления столбцов, до момента выхода в продакшн. В тексте представлена тестовая архитектура, разработанная для принудительного взаимодействия с реальной базой данных, тем самым гарантируя, что тесты проверяют истинное сохранение.Эта архитектура использует явную стратегию очистки EntityManager, общие тестовые фикстуры и изоляцию в памяти. Прокси, предназначенный только для тестирования, оборачивает операции DAO, такие как create(), update() и delete(), автоматически вызывая EntityManager.flush() и EntityManager.clear() после каждой записи. Это заставляет Hibernate записывать изменения в базу данных, а затем очищать кэш L1, гарантируя, что последующие чтения извлекают данные непосредственно из базы данных, а не из кэшированного объекта. Этот уровень прокси эксклюзивен для тестовой области, предотвращая любое влияние на производительность кода продакшена.Для операций чтения, таких как loadById() или пользовательские методы @Query, прокси не вмешивается, поскольку они либо извлекают свежие данные после очистки, либо по своей сути выполняют реальные SQL-запросы. Кроме того, утилита TablesEraser используется для очистки всех таблиц перед каждым тестом, обеспечивая чистую схему и предотвращая проблемы, такие как загрязнение кэша второго уровня или побочные эффекты пакетной обработки между тестами. Этот процесс использует операторы DELETE FROM, соблюдая транзакции и обеспечивая безопасный откат.Повторно используемые абстрактные тестовые классы, такие как AbstractCrudTestCase и AbstractSearchableTestCase, предоставляют общие утверждения для распространенных функций CRUD и поиска. Эти абстрактные классы обрабатывают структурно идентичные тестовые случаи, сокращая объем повторяющегося кода, в то время как конкретные тестовые классы реализуют генерацию полезной нагрузки, специфичной для домена, и добавляют тесты для уникальных методов запросов. Этот многоуровневый подход гарантирует, что базовые классы управляют общим поведением, а конкретные DAO могут расширять и настраивать по мере необходимости.Ключевым примером эффективности этой системы является тестовый случай testSearchNullParams, который специально проверяет граничные условия для операций поиска. Этот тест выявил NullPointerException в ранней версии реализации search() при передаче нулевого объекта Params. Это подтвердило способность дизайна выявлять реальные проблемы до развертывания, обеспечивая надежность.