Spring Boot 개발 중 학습이 필요한 내용을 정리하고,
트러블 슈팅 과정을 기록하는 포스팅입니다.
이번 프로젝트에 Redis
와 DynamoDB
를 활용하고 있습니다.
Redis
와 DynamoDB
를 활용한 Test 환경
을 구축하는 것에서 많은 고민이 들었습니다.
처음으로 생각한 방법은 Test용으로 모든 시스템을 카피해서 생성하는 것이었습니다.
Redis
는 EC2
를 하나 새로 파서 Test
용으로만 올려 놓아서 연결하고,
DynamoDB
또한 Test으로 테이블을 생성하여 연결하는 방법이었습니다.
하지만, 이렇게 되면 cost 측면에서도 손해인 것 같았으며,
각각의 시스템 config의 수정 사항이 있을 때 수정이 상당히 번거롭기 때문에 생산성이 떨어집니다.
때문에 해당 부분에서 대해서 검색을 하면서 큰 도움을 받을 수 있는 티스토리를 찾았습니다.
해당 글에서는 Redis 테스트 환경 설정에 관한 방법을 말하고 있습니다.
(이 티스토리에 들어가면 더 자세한 내용을 볼 수있습니다.)
처음으로 내가 생각한 것과 비슷하게, 로컬에 Redis
를 생성해서 사용하는 방법이 있습니다.
-> 이 방법은 앞서 말했듯, 매번 직접 설치하고 설정해야하기 때문에 생산성이 떨어집니다.
두번째 방법은 Embedded Redis 라이브러리
를 사용하는 방법입니다. Container 기술 이전에는 이 방법이 최선의 방법이었지만, 더 이상은 아니라고 합니다.
Container에 비해 덜 경량화된 방식일 뿐더러, 실제로 해당 라이브러리의 오픈 소스 활동이 중단됐다고 합니다.
마지막으로 오늘의 주제인 TestContainers
라이브러리를 활용하는 것입니다.
TestContainers
는 컨테이너를 기반으로 동작하여 앞서 말한 문제점들을 획기적으로 해결합니다. TestContainers
에 대해서 조금 더 알아 보겠습니다.
TestContainers
는 Docker Container를 활용한 일회용 인스턴스를 제공하는 JUnit 테스트 라이브러리를 말합니다. 쉽게 말해서 테스트를 위해 Docker Container 를 실행시켜주는 자바 라이브러리입니다.
TestContainers
를 활용하면 단순히 테스트 실행만으로 특정 컨테이너가 실행되어 테스트를 진행하고 모든 테스트가 끝나면 컨테이너도 자동으로 종료됩니다. 컨테이너로 동작하기 때문에 어느 환경에서든 바로 실행이 가능합니다.
TestContainers
라이브러리를 활용해서 Redis
와 DynamoDB
컨테이너를 띄운 다음에 Test 환경에서 사용할 수 있도록 ContainerBaseTest
클래스를 만들어보겠습니다.
우선 ContainerBaseTest
클래스는 아래의 IntegrationTest
를 상속받아서 정의했습니다.
IntegrationTest
는 주로 Controller
계층의 Test에 활용되는 통합 테스트 설정 클래스입니다.
@SpringBootTest
@Disabled
@AutoConfigureMockMvc
@ActiveProfiles("test")
@Transactional
public class IntegrationTest {
@Autowired
protected MockMvc mvc;
@Autowired
protected ObjectMapper objectMapper;
}
ContainerBaseTest
클래스는 위의 IntegrationTest
를 상속받아 정의됩니다.
이 후에는 TestContainers
를 기반으로 테스팅 돼야 하는 클래스에서 이 ContainerBaseTest
클래스를 상속받아서 실행하게 되면,
Redis
와 DynamoDB
컨테이너가 자동 생성되며, 테스트 종료시 자동 삭제됩니다.
아래와 같이
redis
와dynamodb
의docker images
값을 설정합니다.
withExposedPorts 메소드
를 통해 각각 컨테이너의 포트를 명시적으로 노출합니다.
withReuse 메소드
설정을 통해 컨테이너를 재사용할 수 있도록 합니다.
@DynamicPropertySource
애노테이션 설정을 통해 실행된 컨테이너의host
,port
값을 가져와 동적으로application
에 설정 값을 매핑합니다.
참고 티스토리
@Testcontainers
public class ContainerBaseTest extends IntegrationTest {
// 로컬의 도커 데스크탑이 실행 중에 있어야 한다!
// redis docker image
private static final String DOCKER_REDIS_IMAGE = "redis:6-alpine";
// dynamodb docker image
private static final String DOCKER_DYNAMODB_IMAGE = "amazon/dynamodb-local:latest";
@ClassRule
static final GenericContainer REDIS_CONTAINER;
@ClassRule
public static GenericContainer DYNAMODB_CONTAINER;
static {
REDIS_CONTAINER = new GenericContainer<>(DOCKER_REDIS_IMAGE)
.withExposedPorts(6379)
.withReuse(true);
DYNAMODB_CONTAINER = new GenericContainer<>(DOCKER_DYNAMODB_IMAGE)
.withExposedPorts(8000)
.withReuse(true);
REDIS_CONTAINER.start();
DYNAMODB_CONTAINER.start();
}
// 동적 설정값 매핑
@DynamicPropertySource
public static void overrideProps(DynamicPropertyRegistry registry){
// redis
registry.add("spring.redis.host", REDIS_CONTAINER::getHost);
registry.add("spring.redis.port", () -> "" + REDIS_CONTAINER.getMappedPort(6379));
// dynamo
final String endpoint = String.format("http://%s:%s", DYNAMODB_CONTAINER.getHost(),
DYNAMODB_CONTAINER.getMappedPort(8000));
registry.add("cloud.aws.dynamodb.endpoint", () -> endpoint);
}
}
DynamoDB
를 TestContainers
라이브러리를 활용해서 테스팅할 때, Table
을 만들고 사용해야합니다!
AWS SDK에서 제공하는 DynamoDBMapper를 활용해서 다음과 같이 테이블을 만들 수 있습니다.
class TextMemoStateApiTest extends ContainerBaseTest {
@Autowired
private DynamoDBMapper dynamoDBMapper;
@Autowired
private AmazonDynamoDB amazonDynamoDb;
@BeforeEach
void setUp() {
// test container 기반, dynamoDB table 생성
CreateTableRequest createTextMemoStateLatestTableRequest = dynamoDBMapper.generateCreateTableRequest(TextMemoStateLatest.class)
.withProvisionedThroughput(new ProvisionedThroughput(1L, 1L));
TableUtils.createTableIfNotExists(amazonDynamoDb, createTextMemoStateLatestTableRequest);
}
// ... 중략