Spring Data 테스트가 놓칠 수 있는 것들: Hibernate의 Persistence Context를 넘어서는 테스트 (7장)
모든 EntityManager는 현재 영속성 컨텍스트 내 엔티티의 ID 맵인 1차 캐시를 유지합니다. 이미 관리 중인 엔티티에 대해 find()가 호출될 때, Hibernate는 데이터베이스를 쿼리하지 않고 기존 객체를 반환하는데, 이는 표준 L1 캐시 동작입니다. 그러나 @DataJpaTest를 사용하는 표준 Spring Data 테스트는 실제 데이터베이스 영속성 대신 이 L1 캐시 상태를 잘못 검증할 수 있습니다. 이는 프로덕션까지 누락된 제약 조건 위반이나 컬럼 매핑 버그와 같은 심각한 문제를 숨길 수 있습니다. 본문은 실제 데이터베이스 상호 작용을 강제하도록 설계된 테스트 아키텍처를 소개하여, 테스트가 진정한 영속성을 검증하도록 보장합니다.이 아키텍처는 명시적인 EntityManager 클리어 전략, 일반 테스트 픽스처, 그리고 인메모리 격리를 활용합니다. 테스트 전용 프록시가 create(), update(), delete()와 같은 DAO 작업을 래핑하여, 각 쓰기 작업 후에 자동으로 EntityManager.flush()와 EntityManager.clear()를 호출합니다. 이는 Hibernate가 변경 사항을 데이터베이스에 기록하도록 강제하고 L1 캐시를 지우도록 하여, 후속 읽기 작업이 캐시된 객체가 아닌 데이터베이스에서 직접 데이터를 검색하도록 보장합니다. 이 프록시 계층은 테스트 범위에만 국한되어 프로덕션 코드에 성능 영향을 미치지 않습니다.loadById() 또는 사용자 정의 @Query 메서드와 같은 읽기 작업의 경우, 프록시는 개입하지 않습니다. 왜냐하면 이러한 작업은 clear 이후 신선한 데이터를 검색하거나 본질적으로 실제 SQL을 발행하기 때문입니다. 또한, TablesEraser 유틸리티를 사용하여 각 테스트 전에 모든 테이블을 비워, 깨끗한 스키마를 보장하고 2차 캐시 오염이나 테스트 간 배치 부작용을 방지합니다. 이 과정은 DELETE FROM 문을 사용하여 트랜잭션을 존중하고 안전한 롤백을 제공합니다.AbstractCrudTestCase 및 AbstractSearchableTestCase와 같은 재사용 가능한 추상 테스트 클래스는 일반적인 CRUD 및 검색 기능에 대한 공유된 어설션을 제공합니다. 이 추상 클래스는 구조적으로 동일한 테스트 케이스를 처리하여 상용구 코드를 줄이는 반면, 구체적인 테스트 클래스는 도메인별 페이로드 생성을 구현하고 고유한 쿼리 메서드에 대한 테스트를 추가합니다. 이 계층적 접근 방식은 기본 클래스가 일반 동작을 관리하고 특정 DAO가 필요에 따라 확장 및 사용자 정의할 수 있도록 보장합니다.이 시스템의 효과를 보여주는 핵심 예시는 검색 작업의 경계 조건을 구체적으로 확인하는 testSearchNullParams 테스트 케이스입니다. 이 테스트는 null Params 객체가 전달되었을 때 search() 구현의 초기 버전에서 NullPointerException을 노출했습니다. 이는 배포 전에 실제 문제를 포착하는 설계의 능력을 검증하여 견고성을 보장했습니다.