컨테이너에서 실행되는 서비스 구성하기

컨테이너에서 실행되는 서비스 구성하기

파일을 컨테이너에 복사하고 컨테이너 안에서 명령을 실행해서, 테스트용 Docker 컨테이너를 초기화하고 구성하는 방법을 배워요.

출처: 문서

본문

이 가이드를 통해 다음 내용을 배울 수 있어요.

  • 파일을 컨테이너에 복사해서 컨테이너 초기화하기
  • execInContainer()로 실행 중인 컨테이너 안에서 명령 실행하기
  • SQL 스크립트로 PostgreSQL 데이터베이스 설정하기
  • LocalStack 컨테이너에 AWS S3 버킷 만들기

사전 준비 (Prerequisites)

  • Java 17 이상
  • 선호하는 IDE
  • Testcontainers가 지원하는 Docker 환경

참고: Testcontainers가 처음이라면 Testcontainers 개요를 방문해 알아보는 걸 권장해요.

파일을 컨테이너에 복사하기

때로는 파일을 특정 위치에 배치해서 컨테이너를 초기화해야 할 때가 있어요. 예를 들어 PostgreSQL은 컨테이너가 시작될 때 /docker-entrypoint-initdb.d/에서 SQL 스크립트를 실행해요.

초기화 스크립트 만들기

src/test/resources/init-db.sql을 만들어요.

create table customers (
    id bigint not null,
    name varchar not null,
    primary key (id)
);

파일을 컨테이너에 복사하기

withCopyFileToContainer()를 사용해 SQL 스크립트를 컨테이너의 init 디렉터리에 복사해요.

package com.testcontainers.demo;

import static org.junit.jupiter.api.Assertions.assertFalse;

import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.postgresql.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.MountableFile;

@Testcontainers
class CustomerServiceTest {
    @Container
    static PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:16-alpine")
        .withCopyFileToContainer(
                MountableFile.forClasspathResource("init-db.sql"),
                "/docker-entrypoint-initdb.d/");

    CustomerService customerService;

    @BeforeEach
    void setUp() {
        customerService = new CustomerService(
                postgres.getJdbcUrl(),
                postgres.getUsername(),
                postgres.getPassword()
        );
    }

    @Test
    void shouldGetCustomers() {
        customerService.createCustomer(new Customer(1L, "George"));
        customerService.createCustomer(new Customer(2L, "John"));

        List<Customer> customers = customerService.getAllCustomers();
        assertFalse(customers.isEmpty());
    }
}

withCopyFileToContainer(MountableFile, String) 메서드는 classpath의 init-db.sql을 컨테이너 안의 /docker-entrypoint-initdb.d/에 복사해요. PostgreSQL은 시작 시 그 디렉터리의 스크립트를 자동으로 실행해요.

호스트의 아무 경로에서도 파일을 복사할 수 있어요.

.withCopyFileToContainer(
        MountableFile.forHostPath("/host/path/to/init-db.sql"),
        "/docker-entrypoint-initdb.d/");

컨테이너 안에서 명령 실행하기

일부 Docker 컨테이너는 작업을 수행하기 위한 CLI 도구를 제공해요. container.execInContainer(String...)을 사용하면 실행 중인 컨테이너 안에서 사용 가능한 어떤 명령이든 실행할 수 있어요.

예시: LocalStack에 S3 버킷 만들기

LocalStack 모듈은 AWS 서비스를 에뮬레이트해요. S3 파일 업로드를 테스트하려면 테스트를 실행하기 전에 버킷을 만들어요.

package com.testcontainers.demo;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.S3;

import java.io.IOException;
import java.net.URI;
import java.util.List;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.Bucket;

@Testcontainers
class LocalStackTest {
    static final String bucketName = "mybucket";

    @Container
    static LocalStackContainer localStack =
            new LocalStackContainer(DockerImageName.parse("localstack/localstack:3.4.0"));

    @BeforeAll
    static void beforeAll() throws IOException, InterruptedException {
        localStack.execInContainer("awslocal", "s3", "mb", "s3://" + bucketName);

        org.testcontainers.containers.Container.ExecResult execResult =
                localStack.execInContainer("awslocal", "s3", "ls");
        String stdout = execResult.getStdout();
        int exitCode = execResult.getExitCode();

        assertTrue(stdout.contains(bucketName));
        assertEquals(0, exitCode);
    }

    @Test
    void shouldListBuckets() {
        URI s3Endpoint = localStack.getEndpointOverride(S3);

        StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
                AwsBasicCredentials.create(localStack.getAccessKey(), localStack.getSecretKey())
        );

        S3Client s3 = S3Client.builder()
                .endpointOverride(s3Endpoint)
                .credentialsProvider(credentialsProvider)
                .region(Region.of(localStack.getRegion()))
                .build();

        List<String> s3Buckets = s3.listBuckets().buckets().stream()
                .map(Bucket::name)
                .toList();

        assertTrue(s3Buckets.contains(bucketName));
    }
}

execInContainer("awslocal", "s3", "mb", "s3://mybucket") 호출은 awslocal CLI 도구(LocalStack 이미지가 제공)를 실행해서 S3 버킷을 만들어요. 어떤 명령이든 출력과 종료 코드를 캡처할 수 있어요.

Container.ExecResult execResult = localStack.execInContainer("awslocal", "s3", "ls");
String stdout = execResult.getStdout();
int exitCode = execResult.getExitCode();

참고: withCopyFileToContainer()와 execInContainer() 메서드는 GenericContainer에서 상속되므로, 모든 Testcontainers 모듈에서 사용할 수 있어요.

요약 (Summary)

  • withCopyFileToContainer()를 사용해 컨테이너가 시작되기 전에 초기화 파일을 컨테이너 안에 배치해요.
  • execInContainer()을 사용해 실행 중인 컨테이너 안에서 버킷, 토픽, 큐 만들기 같은 설정 작업을 위한 명령을 실행해요.

더 읽어보기 (Further reading)

  • Testcontainers for Java 시작하기
  • Testcontainers Postgres 모듈
  • Testcontainers LocalStack 모듈

더 알아보기 (Learn more)