maeng0830
뇌 채우기 운동
maeng0830
전체 방문자
오늘
어제
  • maeng0830-note (89)
    • 자바 (3)
    • 스프링 (39)
      • Core (21)
      • DB (16)
      • Security (2)
      • Test (0)
    • 자료구조 & 알고리즘 (19)
      • 자료구조 (12)
      • 알고리즘 (7)
    • 다른 개발 도구들 (8)
      • Git&Github (1)
      • Redis (3)
      • Docker (4)
    • 프로젝트 (9)
      • Album (9)
    • CS (10)
      • 운영체제 (5)
      • 데이터베이스 (5)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • JPA
  • docker
  • JPQL
  • 트랜잭션
  • 자료구조
  • spring security

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
maeng0830

뇌 채우기 운동

프로젝트/Album

테스트 환경 통합하기

2023. 8. 8. 16:56

Album 프로젝트의 테스트들은 모두 스프링 로드 하에 실행된다.

Presentation 계층의 테스트나 Rest Docs 테스트는 @WebMvcTest를 통해 등록되는 빈을 줄이고 Mock을 사용하긴 하지만, 스프링을 구동하기는 마찬가지이다.

 

이러한 스프링 구동 하에 실행되는 테스트의 장점은 실제 서비스 환경과 매우 유사한 환경에서 테스트를 실행해볼 수 있다는 것이다.

하지만 단점도 있는데, 스프링을 로드하는 과정 때문에 테스트 속도가 비교적 느리다는 것이다.

 

이러한 단점을 보완할 수 있는 방법이 있는 지 여러 블로그들의 글을 뒤적이다가 알게된 사실이 있다.

어찌보면 당연하지만 스프링 통합 테스트의 속도 문제는 스프링 로드 횟수에 가장 큰 영향을 받으며, 각 테스트들이 공통된 환경에서 실행되도록 구현한다면 불필요한 스프링 로드를 줄일 수 있다는 것이다.

이것을 가능하게 해주는 기능이 Context Caching이다.

스프링은 테스트 실행 시 테스트가 사용하는 context를 캐싱한다.

그리고 동일한 context를 사용하는 테스트는 새롭게 스프링을 로드하여 context를 구성하는 것이 아니라, 이전에 캐싱된 context를 사용한다.

 

여러 가지 요인들에 의해 context cache key가 구성되고,

동일한 cache key를 가진 테스트들이 해당 cache key에 해당 하는 context를 함께 사용하게 된다.

가장 대표적인 요인들은 아래와 같다.

  • ActiveProfiles: 어떤 Profiles를 사용하는 지
  • MockBean, SpyBean: 어떤 Bean을 Mocking 하는지
  • Classes: 어떤 클래스들을 로드하는지
  • Property: 어떤 Property를 사용하는 지

 

실제 적용

현재 Album 프로젝트의 테스트 코드는 크게 Persistence, Business, Presentation, Docs 테스트로 구분되어 있다.

 

전체 테스트를 실행하는데 총 5.367초가 소요되었으며, 스프링은 총 14번 로드되었다.

 

실행 환경이 다른 테스트 클래스들이 많다보니, 전체 테스트를 수행할 때 스프링 로드 횟수가 생각보다 많았다.

전체 테스트를 수행할 때 스프링 로드 횟수를 줄이고, 그것을 통해 소요 시간을 감소시킬 필요성이 느껴졌다.

 

Persistence, Business, Presiontation, Docs 테스트 클래스 별로 테스트 실행 환경을 통합하여, 전체 테스트를 수행할 때 스프링이 총 4회만 로드되도록 개선 방향을 잡았다.

 

Persistence 테스트 환경 통합

Persistence 테스트 클래스들이 공통적으로 사용할 테스트 환경을 RepositoryTestSupport에 정의하고,

Persistence 테스트 클래스들이 RepositoryTestSupport를 상속하도록 하였다.

@Import(RepositoryTestConfig.class)
@ActiveProfiles("test")
@DataJpaTest
public abstract class RepositoryTestSupport {

	@Autowired
	protected TestFileManager testFileManager;

	@AfterEach
	void cleanUp() {
		testFileManager.deleteTestFile();
	}
}

 

Business 테스트 환경 통합

Business 테스트 클래스들이 공통적으로 사용할 테스트 환경을 ServiceTestSupport에 정의하고,

Business 테스트 클래스들이 ServiceTestSupport를 상속하도록 하였다.

@Import(ServiceTestConfig.class)
@ActiveProfiles("test")
@Transactional
@SpringBootTest
public abstract class ServiceTestSupport {

	@MockBean
	protected AwsS3Manager awsS3Manager;

	@Autowired
	protected TestFileManager testFileManager;

	@AfterEach
	void cleanUp() {
		testFileManager.deleteTestFile();
	}
}

 

Presentation 테스트 환경 통합

Presentation 테스트 클래스들이 공통적으로 사용할 테스트 환경을 ControllerTestSupport에 정의하고, 

Presentation 테스트 클래스들이 ControllerTestSupport를 상속하도록 하였다.

@Import(ControllerAndDocsTestConfig.class)
@ActiveProfiles("test")
@WebMvcTest(controllers = {AdminController.class, MemberController.class, FollowController.class,
		FeedController.class, CommentController.class},
		includeFilters = @ComponentScan.Filter(classes = {EnableWebSecurity.class}))
public abstract class ControllerTestSupport {
	@Autowired
	protected WebApplicationContext context;

	@Autowired
	protected ObjectMapper objectMapper;

	protected MockMvc mockMvc;

	@Autowired
	protected FileDir fileDir;
	@Autowired
	protected DefaultImage defaultImage;
	@Autowired
	protected TestPrincipalDetailsService testPrincipalDetailsService;

	protected PrincipalDetails memberPrincipalDetails;

	protected PrincipalDetails adminPrincipalDetails;

	@MockBean
	protected MemberService memberService;
	@MockBean
	protected FollowService followService;
	@MockBean
	protected FeedService feedService;
	@MockBean
	protected CommentService commentService;
	@MockBean
	protected AlbumUtil albumUtil;
	@MockBean
	protected FormLoginSuccessHandler formLoginSuccessHandler;
	@MockBean
	protected FormLoginFailureHandler formLoginFailureHandler;
	@MockBean
	protected OAuthLoginSuccessHandler oAuthLoginSuccessHandler;
	@MockBean
	protected OAuthLoginFailureHandler oAuthLoginFailureHandler;

	@Autowired
	protected TestFileManager testFileManager;

	@BeforeEach
	public void setup() {
		mockMvc = MockMvcBuilders
				.webAppContextSetup(context)
				.apply(SecurityMockMvcConfigurers.springSecurity())
				.build();

		memberPrincipalDetails =
				(PrincipalDetails) testPrincipalDetailsService.loadUserByUsername("member");

		adminPrincipalDetails =
				(PrincipalDetails) testPrincipalDetailsService.loadUserByUsername("admin");
	}

	@AfterEach
	void cleanUp() {
		testFileManager.deleteTestFile();
	}
}

 

Docs 테스트 환경 통합

마찬가지로 Docs 테스트 클래스들이 공통적으로 사용할 테스트 환경을 DocsTestSupport에 정의하고,

Docs 테스트 클래스들이 DocsTestSupport를 상속하도록 하였다.

@ActiveProfiles("test")
@Import(ControllerAndDocsTestConfig.class)
@ExtendWith(RestDocumentationExtension.class)
@WebMvcTest(controllers = {AdminController.class, MemberController.class, FollowController.class,
		FeedController.class, CommentController.class})
public abstract class DocsTestSupport {

	@Autowired
	protected WebApplicationContext context;

	protected MockMvc mockMvc;

	@Autowired
	protected ObjectMapper objectMapper;

	@Autowired
	protected FileDir fileDir;
	@Autowired
	protected DefaultImage defaultImage;
	@Autowired
	protected TestPrincipalDetailsService testPrincipalDetailsService;

	protected PrincipalDetails memberPrincipalDetails;

	protected PrincipalDetails adminPrincipalDetails;

	protected BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

	@MockBean
	protected MemberService memberService;
	@MockBean
	protected FollowService followService;
	@MockBean
	protected FeedService feedService;
	@MockBean
	protected CommentService commentService;
	@MockBean
	protected AlbumUtil albumUtil;
	@MockBean
	protected FormLoginSuccessHandler formLoginSuccessHandler;
	@MockBean
	protected FormLoginFailureHandler formLoginFailureHandler;
	@MockBean
	protected OAuthLoginSuccessHandler oAuthLoginSuccessHandler;
	@MockBean
	protected OAuthLoginFailureHandler oAuthLoginFailureHandler;

	@Autowired
	protected TestFileManager testFileManager;

	@BeforeEach
	void setUp(RestDocumentationContextProvider provider) {
		this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
				.apply(documentationConfiguration(provider))
				.build();

		memberPrincipalDetails =
				(PrincipalDetails) testPrincipalDetailsService.loadUserByUsername("member");

		adminPrincipalDetails =
				(PrincipalDetails) testPrincipalDetailsService.loadUserByUsername("admin");
	}

	@AfterEach
	void cleanUp() {
		testFileManager.deleteTestFile();
	}
}

 

결과

테스트 클래스들의 테스트 환경을 통합한 결과, 전체 테스트에서 스프링은 총 4번 로드되었다.

 

예상 외로 전체 테스트 실행 시간은 5.340초로 전후 차이가 거의 없었다.

아마 실행 환경이 통합된 테스트 클래스들의 수가 효과가 나타날 만큼 많지 않았기 때문인 것으로 추측된다.  

 

한 가지 생각해볼 것은 테스트 환경을 통합하는 과정으로 인해 각 테스트 클래스들에게 불필요한 bean들 까지 등록된다는 것이다.

특정한 테스트만 실행할 때에는 이것이 불필요하게 큰 context를 구성하여, 테스트 속도가 느려질 수 있다는 단점이 될 수 있다.

그러나 전체 테스트를 실행할 때에는 테스트 환경의 통합으로 인해 불필요한 스프링 로드를 줄일 수 있다는 점에서 장점이 될 수 있다.

 

context caching은 이러한 장단점을 고려하여, 개발 특성에 맞게 적용해야할 것 같다.   

    '프로젝트/Album' 카테고리의 다른 글
    • Scale Out 환경에서 세션 정합성 문제 해결
    • API 명세서 작성
    • 배포 시 환경변수 관리
    • AttributeConverter 활용
    maeng0830
    maeng0830

    티스토리툴바