영호

[Spring] test context caching 본문

Spring

[Spring] test context caching

0h0 2023. 5. 21. 03:06

잘못된 내용이 있으면 언제든지 피드백 해주시면 감사하겠습니다 ^.^

Context Caching?

Spring에서 테스트를 작성할 때 bean으로 등록한 것들을 테스트하기 위해선 Application context가 필요하다. 하지만 테스트의 독립성을 유지하기 위해 테스트마다 모든 bean으로 구성된 Application context를 만든다면 속도가 매우 느릴것입니다. 그래서 spring의 testContext는 context caching을 해줍니다. 그렇다면 어떤 기준으로 context caching하는지 알아보겠습니다.

언제 새로운 context를 만들지?

기본적으로 textContext는 재사용할 수 있다면 이미 만들어진 context를 caching해 재사용합니다. 그렇다면 언제 재사용을 할까요? 2가지 기준이 있습니다.

  1. 동일한 구성환경을 지닌 테스트
  2. context가 오염되지 않은 경우

위 2가지 경우를 하나씩 살펴보겠습니다.

동일한 구성환경

공식문서에 따르면 Application context를 만드는데 사용된 매개변수들을 조합해 고유하게 식별합니다.

  • locations (from @ContextConfiguration)
  • classes (from @ContextConfiguration)
  • contextInitializerClasses (from @ContextConfiguration)
  • contextCustomizers (from ContextCustomizerFactory) – this includes @DynamicPropertySource methods as well as various features from Spring Boot’s testing support such as @MockBean and @SpyBean.
  • contextLoader (from @ContextConfiguration)
  • parent (from @ContextHierarchy)
  • activeProfiles (from @ActiveProfiles)
  • propertySourceLocations (from @TestPropertySource)
  • propertySourceProperties (from @TestPropertySource)
  • resourceBasePath (from @WebAppConfiguration)

가장 직관적인 예시로 @ContextConfiguration어노테이션을 들 수 있습니다. 현재 2개의 Configuration파일이 있습니다.

@Configuration
public class AConfig {

    @Bean
    public A a() {
        return new A();
    }
}
@Configuration
public class BConfig {

    @Bean
    public B b() {
        return new B();
    }
}

이 상황에서 @ContextConfiguration을 이용해 2개의 테스트가 이용하는 context설정을 다르게 주고 출력문을 통해 context의 고유 키 값을 확인해보겠습니다.

@SpringBootTest
@ContextConfiguration(classes = AConfig.class)
public class ATest {
    
    @Autowired
    ApplicationContext applicationContext;

    @Test
    void a() {
        System.out.println(applicationContext);
    }

    @Test
    void b() {
        System.out.println(applicationContext);
    }
}
@SpringBootTest
@ContextConfiguration(classes = BConfig.class)
public class BTest {

    @Autowired
    ApplicationContext applicationContext;

    @Test
    void a() {
        System.out.println(applicationContext);
    }

    @Test
    void b() {
        System.out.println(applicationContext);
    }
}

2개의 코드는 한 가지만 빼고 모두 똑같습니다. 바로, @ContextConfiguration의 class종류입니다. 위 테스트를 돌렸을 때 나오는 출력문을 확인해보겠습니다.

// ATest
org.springframework.web.context.support.GenericWebApplicationContext@4102b1b1

// BTest
org.springframework.web.context.support.GenericWebApplicationContext@6d67f5eb

고유 식별번호가 다른 것을 확인할 수 있습니다. 그렇다면, @ContextConfiguration을 제거해 동일환 환경에서 context가 구성되도록 하여 context의 고유 번호를 확인해보겠습니다.

@SpringBootTest
public class ATest {
    
    @Autowired
    ApplicationContext applicationContext;

    @Test
    void a() {
        System.out.println(applicationContext);
    }

    @Test
    void b() {
        System.out.println(applicationContext);
    }
}
@SpringBootTest
public class BTest {

    @Autowired
    ApplicationContext applicationContext;

    @Test
    void a() {
        System.out.println(applicationContext);
    }

    @Test
    void b() {
        System.out.println(applicationContext);
    }
}

 

// ATest
org.springframework.web.context.support.GenericWebApplicationContext@50b1f030

// BTest
org.springframework.web.context.support.GenericWebApplicationContext@50b1f030

context구성환경이 동일하기 때문에 하나의 context를 재사용 하는 것을 볼 수 있습니다. @JdbcTest@SpringBootTest등 등록되는 bean의 종류가 다를 때도 context를 caching하지 않고 새로운 context를 만들게 됩니다.

 

@JdbcTest, @SpringBootTest

추가적으로 @SpringBootTest의 webEnvironment옵션에 따른 context caching 여부가 궁금해서 확인해본 결과 @SpringBootTest의 webEnvironment옵션에 따라서 모두 다른 context가 생성됩니다. 물론 webEnvironment가 같아도 구성환경 등이 다르면 다른 context가 생성될 것 입니다.

 

@SpringBootTest와 @JdbcTest구현 클래스를 보면 다양한 어노테이션이 있는데 그 중 @BootStrapWith어노테이션을 보면 parameter가 각각 SpringBootTestContextBootsrapper, JdbcTestContextBootstrapper로 다른 것을 볼 수 있습니다. @BootStrapper공식문서에 따르면 아래와 같이 나옵니다.

@BootstrapWith
 defines class-level metadata that is used to determine how to bootstrap the 
Spring TestContext Framework.

즉, testContext를 구성하는 메타데이터 정보로 @JdbcTest와 @SpringBootTest는 context구성 메타데이터가 다르기 때문에 context caching을 통한 재사용이 발생하지 않습니다.

context오염

기본적으로 테스트를 할 때는 테스트별로 독립적이어야 합니다. 하지만 기존의 context가 오염된 상황에서 각 테스트가 독립적일 수 있을까요?

여기서 오염이란 context내부 빈의 정보가 수정, 추가, 삭제 등 context정보에 변경이 있는 것을 말합니다. 대표적으로 mocking을 예로 들 수 있습니다.

만약 A라는 bean을 mocking하게 되면 context를 구성하던 A라는 bean이 mocking객체로 변경됩니다. 이 과정에서 기존 context의 A빈이 오염됐다고 표현할 수 있습니다. 이로 인해, testContext는 context를 재사용하지 않고 새로운 context를 만듭니다.

 

우선 A라는 객체를 빈으로 등록하겠습니다.

@Component
public class A {
}
@SpringBootTest
public class ATest {

    @Autowired
    ApplicationContext applicationContext;

    @MockBean
    A a;

    @Test
    void a() {
        System.out.println(applicationContext);
    }

    @Test
    void b() {
        System.out.println(applicationContext);
    }
}
@SpringBootTest
public class BTest {

    @Autowired
    ApplicationContext applicationContext;

    @Test
    void a() {
        System.out.println(applicationContext);
    }

    @Test
    void b() {
        System.out.println(applicationContext);
    }
}

위 코드를 수행한 결과를 확인해보겠습니다.

// ATest
org.springframework.web.context.support.GenericWebApplicationContext@7bdb4f17

//BTest
org.springframework.web.context.support.GenericWebApplicationContext@2c0f7678

ATest에서 context를 만들었지만, BTest에서 mocking으로 인해 contextrk

그렇다면 A도 똑같이 mocking하면 어떻게 될까요?

public class ATest {

    @Autowired
    ApplicationContext applicationContext;

    @MockBean
    A a;

    @Test
    void a() {
        System.out.println(applicationContext);
    }

    @Test
    void b() {
        System.out.println(applicationContext);
    }
}
@SpringBootTest
public class BTest {

    @Autowired
    ApplicationContext applicationContext;

	  @MockBean
    A a;

    @Test
    void a() {
        System.out.println(applicationContext);
    }

    @Test
    void b() {
        System.out.println(applicationContext);
    }
}
// ATest
org.springframework.web.context.support.GenericWebApplicationContext@5d10455d

// BTest
org.springframework.web.context.support.GenericWebApplicationContext@5d10455d

BTest는 ATest의 context를 오염시키지 않았고, 구성환경도 동일하기 때문에 context를 재사용한 것을 볼 수 있습니다.

 

또한, @Import역시 context에 빈이 추가된 것이기 때문에 context caching으로 재사용하지 않습니다.

DirtiestContext

@DirtiesContext의 구현 클래스를 들어가보면 주석에 아래와 같은 문구가 있습니다.

Test annotation which indicates that the ApplicationContext associated with a test is dirty and should therefore be closed and removed from the context cache.

만약 Test에 해당 어노테이션이 붙어있으면 구성환경이 동일한 context가 있더라고 context를 재사용하지 않고 새로 만들어서 사용하게 됩니다.

@SpringBootTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
public class ATest {

    @Autowired
    ApplicationContext applicationContext;

    @Test
    void a() {
        System.out.println(applicationContext);
    }

    @Test
    void b() {
        System.out.println(applicationContext);
    }
}
@SpringBootTest
public class BTest {

    @Autowired
    ApplicationContext applicationContext;
    
    @Test
    void a() {
        System.out.println(applicationContext);
    }

    @Test
    void b() {
        System.out.println(applicationContext);
    }
}

위 코드를 보면, @DirtiesContext말고 차이가 없습니다. 그렇다면 젤 처음에 살펴본 예시처럼 모든 구성환경이 같기 때문에 동일한 context를 caching해 재사용할까요?

// ATest
org.springframework.web.context.support.GenericWebApplicationContext@2d83c5a5

// BTest
org.springframework.web.context.support.GenericWebApplicationContext@aee05f4

새로운 context를 생성해 사용하는 것을 확인할 수 있습니다. 이처럼 @DirtiestContext가 붙으면 무조건 새로운 context를 만들게 되는데요. 이것도 메서드 단위로 만들지, 클래스 단위로 만들지 다양하게 선택할 수 있습니다.

Comments