programing

Spring Data 저장소에서 Spring의 선언 캐싱 지원을 테스트하는 방법은 무엇입니까?

lovejava 2023. 9. 16. 08:20

Spring Data 저장소에서 Spring의 선언 캐싱 지원을 테스트하는 방법은 무엇입니까?

Spring Data 저장소를 개발했습니다.MemberRepository인터페이스, 확장되는org.springframework.data.jpa.repository.JpaRepository.MemberRepository메소드가 있습니다.

@Cacheable(CacheConfiguration.DATABASE_CACHE_NAME)
Member findByEmail(String email);

결과는 Spring 캐시 추상화(에 의해 지원됨)에 의해 캐시됩니다.ConcurrentMapCache).

제가 가진 문제는 (hsqldb에 대한) 통합 테스트를 작성하고 싶은 것입니다. 결과를 db에서 첫 번째검색하고 캐시에서 번째로 검색한다는 것을 주장하는 것입니다.

저는 처음에 jpa 인프라(인테리어 매니저 등)를 조롱할 생각을 했는데 어떻게든 엔티티 매니저가 두 번째로 불리는 것이 아니라 너무 어렵고 번거로운 것 같다고 주장합니다(https://stackoverflow.com/a/23442457/536299) 참조).

그러면 다음과 같이 주석이 달린 Spring Data Repository 메서드의 캐싱 동작을 테스트하는 방법에 대한 조언을 제공해 주실 수 있습니까?@Cacheable?

캐싱과 같은 기술적 측면을 테스트하려면 데이터베이스를 전혀 사용하지 마십시오.여기서 테스트하려는 내용을 이해하는 것이 중요합니다.동일한 인수를 사용하는 호출에 대해 메서드 호출을 방지하려고 합니다.데이터베이스 전면의 리포지토리는 이 항목과 완전히 직교하는 측면입니다.

제가 추천하고 싶은 것은 다음과 같습니다.

  1. 선언 캐싱을 구성하는 통합 테스트를 설정하거나 프로덕션 구성에서 필요한 비트와 조각을 가져옵니다.
  2. 리포지토리의 모의 인스턴스를 구성합니다.
  3. 테스트 케이스를 작성하여 예상되는 모의 행동을 설정하고 메소드를 호출한 후 그에 따른 출력을 확인합니다.

견본

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class CachingIntegrationTest {

  // Your repository interface
  interface MyRepo extends Repository<Object, Long> {

    @Cacheable("sample")
    Object findByEmail(String email);
  }

  @Configuration
  @EnableCaching
  static class Config {

    // Simulating your caching configuration
    @Bean
    CacheManager cacheManager() {
      return new ConcurrentMapCacheManager("sample");
    }

    // A repository mock instead of the real proxy
    @Bean
    MyRepo myRepo() {
      return Mockito.mock(MyRepo.class);
    }
  }

  @Autowired CacheManager manager;
  @Autowired MyRepo repo;

  @Test
  public void methodInvocationShouldBeCached() {

    Object first = new Object();
    Object second = new Object();

    // Set up the mock to return *different* objects for the first and second call
    Mockito.when(repo.findByEmail(Mockito.any(String.class))).thenReturn(first, second);

    // First invocation returns object returned by the method
    Object result = repo.findByEmail("foo");
    assertThat(result, is(first));

    // Second invocation should return cached value, *not* second (as set up above)
    result = repo.findByEmail("foo");
    assertThat(result, is(first));

    // Verify repository method was invoked once
    Mockito.verify(repo, Mockito.times(1)).findByEmail("foo");
    assertThat(manager.getCache("sample").get("foo"), is(notNullValue()));

    // Third invocation with different key is triggers the second invocation of the repo method
    result = repo.findByEmail("bar");
    assertThat(result, is(second));
  }
}

보다시피, 우리는 여기서 약간의 과잉 테스트를 합니다.

  1. 가장 관련성이 높은 검사는 두 번째 호출이 첫 번째 개체를 반환하는 것이라고 생각합니다.캐싱이 바로 그런 것입니다.키가 같은 첫 번째 두 호출은 동일한 개체를 반환하는 반면 키가 다른 세 번째 호출은 저장소에서 두 번째 실제 호출로 이어집니다.
  2. 캐시가 첫 번째 키에 대한 값을 실제로 가지고 있는지 확인하여 테스트 케이스를 강화합니다.실제 값을 확인하기 위해 그것을 확장할 수도 있습니다.다른 한편으로는 응용 프로그램 수준의 동작보다는 메커니즘의 내부를 더 많이 테스트하는 경향이 있기 때문에 이를 피하는 것도 괜찮다고 생각합니다.

키 테이크어웨이

  1. 컨테이너 동작을 테스트하기 위한 인프라가 필요하지 않습니다.
  2. 테스트 케이스를 쉽게 설정할 수 있습니다.
  3. 잘 설계된 구성요소를 사용하면 간단한 테스트 사례를 작성할 수 있고 테스트에 필요한 통합 작업이 적습니다.

Oliver의 예를 이용하여 제 앱에서 캐시 동작을 테스트해 보았습니다.제 경우에는 제 캐시가 서비스 계층에 설정되어 있고 제 레포가 올바른 횟수만큼 호출되고 있는지 확인하고 싶습니다.저는 모의실험 대신에 모의실험을 하고 있습니다.저는 먼저 실행되는 테스트가 캐시를 채우고 다른 테스트에 영향을 미친다는 것을 깨닫기 전까지 테스트가 실패하는 이유를 파악하는 데 시간을 보냈습니다.모든 테스트의 캐시를 지운 후 예상대로 동작하기 시작했습니다.

제가 생각한 바는 이렇습니다.

@ContextConfiguration
class FooBarServiceCacheTest extends Specification {

  @TestConfiguration
  @EnableCaching
  static class Config {

    def mockFactory = new DetachedMockFactory()
    def fooBarRepository = mockFactory.Mock(FooBarRepository)

    @Bean
    CacheManager cacheManager() {
      new ConcurrentMapCacheManager(FOOBARS)
    }

    @Bean
    FooBarRepository fooBarRepository() {
      fooBarRepository
    }

    @Bean
    FooBarService getFooBarService() {
      new FooBarService(fooBarRepository)
    }
  }

  @Autowired
  @Subject
  FooBarService fooBarService

  @Autowired
  FooBarRepository fooBarRepository

  @Autowired
  CacheManager cacheManager

  def "setup"(){
    // we want to start each test with an new cache
    cacheManager.getCache(FOOBARS).clear()
  }

  def "should return cached foobars "() {

    given:
    final foobars = [new FooBar(), new FooBar()]

    when:
    fooBarService.getFooBars()
    fooBarService.getFooBars()
    final fooBars = fooBarService.getFooBars()

    then:
    1 * fooBarRepository.findAll() >> foobars
  }

def "should return new foobars after clearing cache"() {

    given:
    final foobars = [new FooBar(), new FooBar()]

    when:
    fooBarService.getFooBars()
    fooBarService.clearCache()
    final fooBars = fooBarService.getFooBars()

    then:
    2 * fooBarRepository.findAll() >> foobars
  }
} 

언급URL : https://stackoverflow.com/questions/24221569/how-to-test-springs-declarative-caching-support-on-spring-data-repositories