Testcontainers를 활용한 AWS S3 테스트 환경 구성하기
by rowing0328Intro
이번 포스팅에서는 Testcontainers을 활용해 AWS S3 테스트 환경을 구성하고, 기존 laaS 기반 테스트와 비교해본다.
LocalStack은 클라우드 서비스를 로컬에서 에뮬레이션할 수 있는 강력한 도구로, AWS S3, Lambda, DynamoDB 등 다양한 서비스를 테스트할 수 있도록 지원한다.
두 가지 방식으로 테스트 환경을 구축하며 각각의 장단점, 적용 상황을 구체적으로 살펴보고, 이를 통해 얻은 교훈과 인사이트를 공유한다.
laaS 기반 LocalStack 테스트
IaaS(Infrastructure as a Service) 기반 테스트는 외부 서버에서 Docker를 통해 LocalStack을 실행하고 이를 테스트 환경으로 활용하는 방법이다.
구성 방법
- 외부 서버를 설정한다. AWS EC2와 같은 IaaS 환경에 Docker를 설치한다.
- docker-compose.yml 파일을 작성해 LocalStack을 실행한다.
- Spring Boot에서 S3 클라이언트를 구성해 LocalStack과 연동한다.
docker-compose.yml
services:
localstack:
image: localstack/localstack:latest
container_name: localstack
ports:
- "4566:4566"
environment:
- SERVICES=s3
- AWS_ACCESS_KEY_ID=test
- AWS_SECRET_ACCESS_KEY=test
- TZ=Asia/Seoul
TestLocalStackConfig.class
- 관련 테스트 코드는 참고 자료를 통해 확인할 수 있다. 필요한 경우 해당 내용을 확인해 보자.
@Validated
@Profile("test")
@TestConfiguration
public class TestLocalStackConfig {
private final String region;
private final String bucket;
private final String endpoint;
private final String accessKey;
private final String secretKey;
public TestLocalStackConfig(
@NotNull @Value("${localstack.region.static}") String region,
@NotNull @Value("${localstack.s3.bucket}") String bucket,
@NotNull @Value("${localstack.s3.endpoint}") String endpoint,
@NotNull @Value("${localstack.credentials.access-key}") String accessKey,
@NotNull @Value("${localstack.credentials.secret-key}") String secretKey
) {
this.region = region;
this.bucket = bucket;
this.endpoint = endpoint;
this.accessKey = accessKey;
this.secretKey = secretKey;
}
@Bean
public AmazonS3 amazonS3() {
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
AmazonS3 amazonS3 = AmazonS3ClientBuilder.standard()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endpoint, region))
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withPathStyleAccessEnabled(true)
.build();
if (!amazonS3.doesBucketExistV2(bucket)) {
amazonS3.createBucket(bucket);
}
return amazonS3;
}
}
장점
- 리소스 분리
로컬 개발 환경과 독립적으로 동작해 팀 간 충돌을 방지할 수 있다. - 팀 협업 용이
외부 서버를 통해 모든 사용자가 동일한 환경을 공유한다. - 확장성
대규모 팀 환경에서도 쉽게 확장 가능하다.
단점
- 관리 부담
외부 서버와 Docker 설정을 별도로 관리해야 한다. - 테스트 충돌 가능성
여러 사용자가 동일한 환경에서 작업하면 멱등성이 보장되지 않을 수 있다. - 비용
클라우드 인프라를 사용하는 데 따른 추가 비용이 발생한다.
Testcontainers 방식의 LocalStack 테스트
Testcontainers는 테스트 코드 내에서 Docker 컨테이너를 동적으로 관리하는 오픈소스 라이브러리이다.
LocalStack과 결합하면 외부 서버 없이 로컬에서 AWS S3 테스트 환경을 구성할 수 있다.
구성 방법
- Testcontainers 의존성을 프로젝트에 추가한다.
- 테스트에서 S3 클라이언트를 컨테이너와 연동한다.
- LocalStackContainer를 설정하고 초기화한다.
TcAwsS3HandlerTest.class
- 전체 테스트 코드는 참고 자료를 통해 확인할 수 있다. 필요한 경우 해당 내용을 확인해 보자.
@Testcontainers
@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TcAwsS3HandlerTest {
private static final DockerImageName LOCALSTACK_IMAGE_NAME = DockerImageName.parse("localstack/localstack:latest");
private static final String LOCALSTACK_URI = "http://localhost:4566/";
@Container
public static final LocalStackContainer localStack = new LocalStackContainer(LOCALSTACK_IMAGE_NAME)
.withServices(Service.S3);
@Value("${localstack.s3.bucket}")
private String bucket;
private AmazonS3 s3Client;
private AwsS3Handler awsS3Handler;
@BeforeEach
void setUp() throws IOException {
s3Client = AmazonS3ClientBuilder
.standard()
.withEndpointConfiguration(
new AwsClientBuilder.EndpointConfiguration(
localStack.getEndpoint().toString(),
localStack.getRegion()
)
)
.withCredentials(
new AWSStaticCredentialsProvider(
new BasicAWSCredentials(localStack.getAccessKey(), localStack.getSecretKey())
)
)
.build();
s3Client.createBucket(bucket);
awsS3Handler = new AwsS3HandlerImpl(LOCALSTACK_URI, bucket, s3Client);
}
}
장점
- 테스트 독립성
테스트마다 새로운 컨테이너를 생성해 멱등성을 유지한다. - 설정 간소화
외부 서버 없이 로컬에서 모든 테스트를 실행할 수 있다. - 유연성
다양한 AWS 서비스를 동적으로 테스트할 수 있다.
단점
- 로컬 리소스 의존성
CPU와 메모리 사용량이 증가할 수 있다. - 초기 속도 지연
컨테이너 생성과 초기화로 인해 처음 실행 시 시간이 더 걸릴 수 있다.
왜 컨테이너 생성과 초기화가 느릴까
Testcontainers는 Docker 컨테이너를 동적으로 생성하고 초기화하는 과정에서 다음과 같은 작업을 수행한다.
- 이미지 폴링
Docker 허브에서 LocalStack 이미지를 다운로드하는 작업이 필요하다.
이 과정은 캐싱되지 않은 상태에서는 네트워크와 디스크 I/O에 영향을 받는다. - 컨테이너 초기화
컨테이너 내부에서 LocalStack 서비스(S3, Lambda 등)를 실행하고 준비 상태를 확인해야 한다.
이 과정은 Docker엔진과 LocalStack 서비스의 시작 속도에 따라 다소 시간이 걸릴 수 있다.
이러한 초기화 작업은 캐싱되지 않은 상태에서 처음 테스트를 실행할 때 더 느리게 느껴질 수 있다.
캐싱 이후 테스트는 얼마나 빨라질까?
로컬 환경에서는 Docker가 컨테이너와 이미지를 캐싱하기 때문에 초기 실행 이후에는 테스트 속도가 크게 향상된다.
캐싱 전 테스트 결과
캐싱 이후 테스트 결과
이처럼 Testcontainers는 초기 실행에서는 느리지만, Docker의 캐싱 덕분에 이후 테스트 속도는 laaS 기반 방식과 비슷하거나 더 빠르게 느껴질 수 있다.
laaS 기반 테스트와 Testcontainers 비교
어떤 상황에서 선택해야 하는가
Testcontainers는 독립적이고 멱등성이 중요한 로컬 테스트 환경을 구성할 때 적합하다.
외부 서버 없이 모든 테스트를 코드로 관리할 수 있어 설정이 간편하며, CI/CD 파이프라인에도 쉽게 통합할 수 있다.
laaS 기반 테스트는 대규모 팀 환경에서 동일한 서버를 공유하며 테스트해야 할 때 유리하다.
로컬 리소스 사용을 줄이고 외부 서버를 활용하려는 경우 적합하다.
마무리
이번 포스팅에서는 LocalStack을 활용한 IaaS 기반 테스트와
Testcontainers를 활용한 테스트 환경의 차이를 비교하고, 전환 과정을 통해 얻은 인사이트를 공유했다.
Testcontainers로의 전환은 관리 포인트를 코드로 통합하고,
멱등성을 자연스럽게 보장한다는 점에서 특히 유용했다.
또한, Docker의 캐싱 기능 덕분에 초기 실행 속도 문제를 효과적으로 완화할 수 있었으며,
테스트 시간이 1.2초에서 0.53초로 단축되어 CI/CD 파이프라인 성능 개선에도 기여했다.
이번 테스트 환경 구축 과정은 코드로 관리되는 테스트 환경이
개발 생산성과 품질을 동시에 높일 수 있다는 점을 보여주는 사례였다.
테스트 독립성, 간소화된 설정, 멱등성이 중요한 환경이라면
Testcontainers와 LocalStack은 최적의 선택이 될 수 있다.
참고 자료 :
Testcontainers for Java - LocalStack Module
'📌ETC > Development Log' 카테고리의 다른 글
SonarQube와 GitHub Actions로 정적 코드 품질 관리하기 (0) | 2025.01.21 |
---|---|
Spring Boot와 LocalStack으로 AWS S3 테스트 환경 구성하기 (0) | 2025.01.20 |
Spring Boot와 AWS S3 & CloudFront로 파일 관리 설계하기 (0) | 2025.01.16 |
K6와 InfluxDB를 활용한 성능 테스트 및 모니터링 설계하기 (2) | 2025.01.02 |
Prometheus와 Grafana 활용한 Spring Boot 모니터링 설계하기 (0) | 2025.01.02 |
블로그의 정보
코드의 여백
rowing0328