프로젝트의 테스트 코드를 작성하다 전체 테스트 수행 시 다소 시간이 많이 걸리는 문제를 해결했던 경험에 대한 글이다.
테스트 구조 / 테스트 환경 통합
@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class FilesApiControllerTest {
/* 생략 */
}
@ExtendWith(MockitoExtension.class)
public class PostServiceTest {
@InjectMocks
private PostServiceImpl postService;
/* 생략 */
}
@DataJpaTest
public class RecommendPostRepositoryTest {
/* 생략 */
}
기존의 테스트 구조는 다음과 같다.
- Controller 는 @SpringBootTest 를 이용해 통합 테스트를 진행한다.
- Service 는 의존성을 모두 목킹한 뒤 Spring 없이 Service 클래스를 테스트한다.
- Repository 는 @DataJpaTest 를 이용해 Jpa 관련 빈만 등록해 테스트를 진행한다.
하지만 200 개가 넘는 테스트를 한번에 실행하니 시간이 많이 걸려 개선하고 싶었다. 아래의 영상은 변경 전 모든 테스트를 수행한 영상이며 약 1분의 시간이 소요됨을 볼 수 있다.
동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.3)
테스트가 수행되는 것을 보면 위 Spring Boot 로그가 2번 뜨는걸 볼 수 있다. 즉, Spring 서버가 2번 뜨는 것이다.
Spring 은 테스트 시 등록된 빈에 변경사항이 있다면 Spring 서버를 새로 띄운다.
현재 구조에서 Controller 는 @SpringBootTest 를 사용하고 Repository 는 @DataJpaTest 를 사용했다. 즉, Controller 를 테스트할 때와 Repository 를 테스트할 때 환경이 달라지고 등록된 빈에 변경이 발생하기 때문에 Spring 이 두번 뜨는 것이다. @DataJpaTest 가 Jpa 관련된 빈만 등록하여 성능에 이점이 있을것이라 판단했지만 오히려 전체 테스트 시 Spring 서버를 두번 띄워 성능을 느리게 했다.
이를 해결하기 위해 테스트 환경을 통합할 수 있도록 @SpringBootTest 가 붙은 클래스를 만들고 각각의 테스트들이 이를 상속한다.
@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class IntegrationTestSupporter {
}
@Transactional
public class AlarmRepositoryTest extends IntegrationTestSupporter {
/* 생략 */
}
public class MemberApiControllerTest extends IntegrationTestSupporter {
/* 생략 */
}
위 코드처럼 Controller 와 Repository 의 테스트 환경을 통합했고 @SpringBootTest 의 경우 @DataJpaTest 와 달리 @Transactional 이 없기 때문에 Repository 테스트 시 따로 추가해주었다.
만약 통합 테스트 환경에서 무언가를 목킹해야된다면 IntegrationTestSupporter 에서 @MockBean 등을 이용해 목킹하면 모든 테스트에서 목킹된 환경을 사용할 수 있다. 아래는 테스트 환경 변경 후 전체 테스트를 수행한 결과이며 기존보다 약 10초정도 감소됐음을 볼 수 있다.
동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.
Embedded Redis 도입
두 번째로 기존의 테스트는 TestContainer 를 이용해 Spring 서버가 뜰 때 마다 Docker 의 Redis 컨테이너를 새로 만들고 삭제했다. 기존의 테스트에선 스프링 서버가 2번 떳으며 뜰 때마다 Docker 의 Redis 컨테이너를 만들고 지웠다. 이는 성능에도 문제를 주었지만 테스트를 수행하는 사람의 PC 에 Docker 가 깔려있어야 테스트를 수행할 수 있었다. 나 역시 테스트를 수행하기 위해 항상 Docker 를 켜주어야 했고 테스트를 수행하고 컨테이너를 만들 때 꽤 오래 지체되는게 체감되었다.
이를 해결하기 위해 TestContainer 가 아닌 Embedded Redis 를 도입하였다. Embedded Redis 는 테스트 환경에서 Redis 를 설치하거나 Docker 를 사용할 필요 없이 Redis 환경을 구축할 수 있도록 한다.
// redis Mock
testImplementation "org.testcontainers:testcontainers:1.19.1"
testImplementation "org.testcontainers:junit-jupiter:1.19.1"
build.gradle 에 dependency 를 추가하고
@Configuration
@Profile("test")
public class MockRedisConfig {
private final RedisServer redisServer = new RedisServer(6379);
@PostConstruct
public void startRedis() {
this.redisServer.start();
}
@PreDestroy
public void stopRedis() {
this.redisServer.stop();
}
@Bean
@Primary
public RedisConnectionFactory redisConnectionFactoryMock() {
return new LettuceConnectionFactory("localhost", 6379);
}
@Bean
@Primary
public RedisTemplate<String, String> redisTemplateMock() {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactoryMock());
return redisTemplate;
}
}
레디스 환경을 목킹할 수 있는 테스트 Config 클래스에서 TestContainer 가 아닌 Embedded Redis 의 RedisServer 를 이용해 레디스 환경을 구축한다.
Embedded Redis 를 사용했을 때 장점으로 Docker 컨테이너를 생성하지 않아도 사용이 가능해 성능상 이점도 있지만 테스트를 수행하는 사람의 PC 에 Docker 가 없어도 테스트를 수행할 수 있다는 장점이 있다. 이로써 외부 환경에서 더욱 분리될 수 있다.
아래의 영상은 Embedded Redis 까지 도입한 후 전체 테스트를 수행한 결과이며 약 12초 정도 감소됨을 볼 수 있다. Embedded Redis 를 사용하기 전과 후의 경우 약 3초 정도 줄었으며 시간만 보면 유의미해보이지 않지만 직접 테스트를 수행하면 TestContainer 를 실행할 때 멈춰있는 현상이 없어져 체감이 크게 됐고 Embedded Redis 를 도입하지 않았다면 Spring 서버가 새로 실행될 때마다 3초가 지연된다고 생각하면 크게 느껴진다.
동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.
'Project' 카테고리의 다른 글
| [Independe] Main Post 첫 번째 성능개선 (1) | 2024.03.11 |
|---|---|
| [TickerBell] 백엔드 협업 구조 (1) | 2024.01.29 |
| [Independe] 협업을 위한 첫걸음 Swagger (0) | 2023.09.15 |
| [Independe] 자취생 커뮤니티 프로젝트 기획 (0) | 2023.09.13 |