Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

테스트 컨테이너 학습하기 #2

Open
pbg0205 opened this issue Dec 12, 2022 · 3 comments
Open

테스트 컨테이너 학습하기 #2

pbg0205 opened this issue Dec 12, 2022 · 3 comments

Comments

@pbg0205
Copy link
Owner

pbg0205 commented Dec 12, 2022

참고 내용

@pbg0205
Copy link
Owner Author

pbg0205 commented Dec 12, 2022

test container reference : https://www.testcontainers.org/

1. 테스트 컨테이너의 편리한 경우

  1. 데이터 엑세스 계층 통합 테스트
    • 운영과 동일한 상태로 개발 가능
    • 간단한 설정을 통한 컨테이너 생성 가능
  2. 애플리케이션 통합 테스트
    • 다양한 의존성 있는 컴포넌트(e.g. RDB, MQ) 를 편리하게 테스트해볼 수 있음
  3. UI/ 승인 테스트
    • 자동화된 UI 테스트 가능(Selenium 과 호환 가능?)

테스트 컨테이너의 단점

  • 테스트 수행 속도가 낮아진다.(컨테이너를 한번만 인스턴스화 하면 해결이 가능하다.)

JDBC 를 사용한다면 간단히 설정 가능

spring:
  datasource:
    url: jdbc:tc:mysql:8:///soolsul?TC_REUSABLE=true
    username: test
    password: test121212
    driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver

Redis의 경우, 별도로 만들어줘야 함.

public abstract class TestRedisContainer {

    private static final String DOCKER_REDIS_IMAGE = "redis:7.0.5-alpine";

    public static GenericContainer REDIS_CONTAINER =
            new GenericContainer<>(DockerImageName.parse(DOCKER_REDIS_IMAGE))
                    .withExposedPorts(6379)
                    .withReuse(true);

    static {
        REDIS_CONTAINER.start();

        System.setProperty("spring.redis.host", REDIS_CONTAINER.getHost());
        System.setProperty("spring.redis.port", REDIS_CONTAINER.getMappedPort(6379).toString());
    }
}

@pbg0205
Copy link
Owner Author

pbg0205 commented Dec 12, 2022

https://www.testcontainers.org/quickstart/junit_5_quickstart/

JUnit 5 Quickstart

1. 테스트 컨테이너가 없을 떄의 문제점

local installation 환경의 문제점
image

  • local installation 에 의존한다.
  • 모든 개발자가 모두 같은 설치 버전으로 테스트를 한다는 보장이 없다. (같은 로컬 환경의 테스트가 필요하다)

1. Add Testcontainers as a test-scoped dependency

  • gradle 에서 아래와 의존성을 추가하면 JUnit5 와 통합된다. (통합된다는 의미가 뭐지...?)
testImplementation "org.junit.jupiter:junit-jupiter:5.8.1"
testImplementation "org.testcontainers:testcontainers:1.17.6"
testImplementation "org.testcontainers:junit-jupiter:1.17.6"

2. Get Testcontainers to run a Redis container during our tests

@Container
public GenericContainer redis = new GenericContainer(DockerImageName.parse("redis:5.0.3-alpine"))
    .withExposedPorts(6379);
  • @container (아직 뭔 소리인지 모르겠다.)
    • 테스트 생명 주기의 다양한 이벤트에 대해 해당 필드에 알리도록 JUnit 에게 지시한다.
    • 해당 객체는 Docker Hub에 특정 Redis 이미지를 사용하도록 구성하고 포트를 노출하도록 구성된 TestContainers GenericContainer 이다.

테스트 실행 시 TestContainer 는 아래와 같은 로그를 보여준다.

  • 테스트 메서드가 실행하기 이전 활성화 되었다는 메세지
  • local docker setup 을 확인하고 빠르게 테스트 한다는 메세지
  • 필요한 경우 image 를 pull 한다는 메세지
  • 새로운 컨테이너를 생성하고 준비하기까지 대기하는 상태
  • 테스트 이후 컨테이너를 종료 및 삭제하는 상태

3. Make sure our code can talk to the container

  • 테스트 컨테이너는 각가 컨테이너마다 랜덤한 포틀르 사용하지만 런타임 실제 포트도 편리하게 사용할 수 있다.
  • [notice] 웬만하면 getHost() 메서드를 사용하자. localhost의 경우 일부 환경에서만 동작하기 때문에 다른 환경에서 동작하지 않을 수 있음(e.g. CI 환경)
@Testcontainers
public class RedisBackedCacheIntTest {

    private RedisBackedCache underTest;

    // container {
    @Container
    public GenericContainer redis = new GenericContainer(DockerImageName.parse("redis:5.0.3-alpine"))
        .withExposedPorts(6379);

    // }

    @BeforeEach
    public void setUp() {
        String address = redis.getHost();
        Integer port = redis.getFirstMappedPort();

        // Now we have an address and port for Redis, no matter where it is running
        underTest = new RedisBackedCache(address, port);
    }

    @Test
    public void testSimplePutAndGet() {
        underTest.put("test", "example");

        String retrieved = underTest.get("test");
        assertThat(retrieved).isEqualTo("example");
    }
}

@pbg0205
Copy link
Owner Author

pbg0205 commented Dec 13, 2022

사이드 리뷰 피드백

1. @DynamicPropertySource(참고 레퍼런스 : https://recordsoflife.tistory.com/607)

  • Spring ver. 5.2.5 이후부터 나온 어노테이션이며, 동적 값으로 속성(property)을 수정을 지원합니다.
  • Dynamic PropertyRegistry.add(String, Supplier) 메서드를 통해 Spring Enviorment 일부 속성을 추가합니다.
    @SpringBootTest
    @Testcontainers
    public class ArticleLiveTest {
    
        @Container
        static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:11")
          .withDatabaseName("prop")
          .withUsername("postgres")
          .withPassword("pass")
          .withExposedPorts(5432);
    
        @DynamicPropertySource
        static void registerPgProperties(DynamicPropertyRegistry registry) {
            registry.add("spring.datasource.url", 
              () -> String.format("jdbc:postgresql://localhost:%d/prop", postgres.getFirstMappedPort()));
            registry.add("spring.datasource.username", () -> "postgres");
            registry.add("spring.datasource.password", () -> "pass");
        }
        
        // tests are same as before
    }
    1. @DynamicPropertySource 어노테이션을 선언한 메서드는 정적으로 선언해야 합니다.
    2. 그리고 DynamicPropertyRegistry 유형 인수(Parameter) 하나만 허용해야 합니다.

    2. 대안 테스트 설비

    1. TestContainer, @DynamicPropertySource 모두 고정 장치 설정과 테스트 코드에 대한 의존도가 높아지는 단점이 있습니다.
    2. 인프라 설정 및 동적 구성 적용은 이를 필요로 하는 모든 테스트에서 복제됩니다.
    3. 이러한 단점을 피하기 위해 대부분의 테스트 프레임워크에서 제공하는 테스트 픽스처 기능을 사용할 수 있습니다.
      • Q. 테스트 픽스처?? 모든 테스트 전에 RDB 구성 + SpringBoot 구성, 테스트 이후 인스턴스 중지를 설정할 수 있습니다
      • 대표적인 예로는 BeforeAll, AfterAll 메서드가 있습니다. 그리고 이를 재사용하기 위해서 @ExtendsWith(---Extension.class) 를 사용하면 동일한 기능을 쉽게 재사용할 수 있습니다.

    3. 궁금증

    • @DynamicPropertySource 로 Spring Enviorment 설정하면 만약 재실행을 시작하지 않는 경우에는 메인 코드에는 영향을 미치지 않는 것일까? (어노테이션을 확인해보니 런타임 시점에 property 를 덮어씌우는 기능을 하는 것 같음.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant