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

test: TestContainers 적용 #583

Merged
merged 4 commits into from
Dec 7, 2023
Merged

test: TestContainers 적용 #583

merged 4 commits into from
Dec 7, 2023

Conversation

dooboocookie
Copy link
Collaborator

📌 관련 이슈

🛠️ 작업 내용

  • MySQL TestContainers 적용하여 테스트 MySQL 환경에서 할 수 있도록 수정

🎯 리뷰 포인트

🤔 문제점

저희는 지금까지 테스트를 인메모리 DB인 H2를 활용하여 하고 있었습니다.
H2 환경은 실제 프로덕트와 같은 MySQL 환경과는 다른 점이 존재한다.

  1. @transactional(readOnly=true) 같은 옵션이 벤더사마다 다르게 적용된다 블로그
  2. 데이터 타입이 다를 수 있다. → 추후 공간 인덱스 적용할 때 지오메트리 타입을 적용해야함
  3. 문법이 다를 수 있다. 네이티브 쿼리 사용할 때 지원을 안할 수 있음 → 사실 비관적 락 관련해서 걱정돼서 먼저 살펴본건데, SELECT FOR UPDATE는 H2에서도 잘 적용된다고 하더라구요.

😳 선택지

MySQL을 테스트 환경에서 띄우기 위해서는 세가지 방법 정도가 존재했습니다.

1. 로컬에 직접 MySQL을 직접 실행한다

장점

  • MySQL을 직접 설치하든, Docker를 활용하여 띄우든 테스트가 도는 동안 새로 도커를 띄우지 않기 때문에, 불필요한 시간을 사용하지 않는다.
    단점
  • 팀원들끼리 환경을 맞추기가 어렵다.
  • 포트라던지, Url, username, ... 등을 맞추기 어렵기 때문에 같은 local 프로필을 사용하기가 어렵다.
  • GithubActions같은 환경에서 테스트하기 위해서는 플러그인을 사용해서 띄울 수 있지만, 이것은 CI툴을 변경하면 또 변경해야되는 방법이다.
  • 해당 DB가 언제 띄워진 것인지 보장이 없기 때문에 내부 데이터가 오염되어있을 수 있다(이것은 truncate을 하기 때문에 지금은 괜찮아 보입니다)

2. DockerFile이나 Docker-Compose를 이용해서 MySQL을 도커로 띄운다.

장점

  • DockerFile이나 Docker-Compose 같은 설정 파일로 설정 공유가 가능하다
  • DB를 매번 새로 띄우기 때문에, 오염 걱정을하지 않아도 된다.
  • GitHubActions의 Ubuntu 환경에서도 바로 적용이 가능하다.
    단점
  • 도커 컨테이너를 띄우는 데 시간이 오래걸린다.
  • 도커 컨테이너의 생명주기를 커맨드 같은것을 이용해서 직접 관리해줘야한다.

3. Testcontainers 라이브러리를 이용해서 DB를 띄운다.

Testcontainers는 테스트 환경을 구성하기 위해 독립적인 컨테이너를 관리해주는 라이브러리
데이터베이스, 메시지 브로커, 웹 서버 등의 외부 리소스를 테스트할 때 효과적이라고 합니다.

장점

  • 위 2번의 장점을 다 공유한다.
  • 도커 컨테이너의 생명주기를 직접 관리해줄 필요 없이 테스트가 종료 되면 컨테이너가 자동으로 종료된다.
  • 컨테이너에 대한 설정을 JAVA 코드로 할 수 있어서 설정이 편하다.

단점

  • 라이브러리 추가가 필요한다.
  • Testcontainers 컨테이너를 띄우고 mysql 컨테이너를 실행하기 때문에 속도가 가장 느리다고 볼 수 있다.

‼그래서 뭘하지?

일단은 Testcontainers를 사용해보았습니다.
제 생각엔 테스트를 세팅하기 위해서 테스트 컨테이너 한번을 띄우는 것은 테스트 속도에 엄청나게 영향을 끼치지 않는다고 생각했습니다.
그리고 1번에서의 설명한 단점들이 전부 치명적이라고 생각해서
코드로 설정을 공유할 수 있는 3번을 선택했습니다!!

👍 적용

적용 대상은 모두로 결정하였습니다!

위에서 다룬 문제점 말고 저희가 H2를 사용할 때 추적하지 못하는 부분이 있을것이라 생각했습니다.
그래서 운영 환경과 같은 환경을 구성하기 위해서 MySQL을 이용한 테스트를 DB를 사용하는 테스트에 모두 적용하기로 했습니다.

classDiagram
    AbstractTest <|-- MySqlContainerTest
    AbstractTest <|-- ControllerTest
    AbstractTest <|-- ServiceTest
    MySqlContainerTest <|-- MySqlContainerControllerTest
    MySqlContainerTest <|-- MySqlContainerServiceTest
    class MySqlContainerTest{
        +MySQLContainer mySqlContainer
        +mySqlProperties(Registry registry)
    }
Loading

그래서 현재는 위에서 보이는 MySqlContainerServiceTestMySqlContainerControllerTest를 테스트들에 상속해놨습니다.

코드 내용은 복잡하지 않아 간단히 설명하면

public abstract class MySqlContainerTest extends AbstractTest {

    static final MySQLContainer<?> mySqlContainer = new MySQLContainer<>("mysql:8.0.35");

    @DynamicPropertySource
    static void mySqlProperties(final DynamicPropertyRegistry registry) {
        mySqlContainer.start();
        registry.add("spring.datasource.url", mySqlContainer::getJdbcUrl);
        registry.add("spring.datasource.username", mySqlContainer::getUsername);
        registry.add("spring.datasource.password", mySqlContainer::getPassword);
        registry.add("spring.datasource.driver-class-name", mySqlContainer::getDriverClassName);
    }
}

이 클래스만 보면되는데요.

static final MySQLContainer<?> mySqlContainer = new MySQLContainer<>("mysql:8.0.35");

이것은 mysql:8.0.35버전을 통해서 컨테이너를 구성한는 것이구요.

mySqlContainer.start();

컨테이너를 실행하겠다는 의미입니다. 이미지를 풀 받고 컨테이너를 실행하는 작업을 하겠죠.
포트번호를 따로 설정해두지 않았는데요. 그럼 이때마다 포트번호가 동적으로 바뀌고 url도 바뀔 수 있겠죠?

    @DynamicPropertySource
    static void mySqlProperties(final DynamicPropertyRegistry registry) {
        mySqlContainer.start();
        registry.add("spring.datasource.url", mySqlContainer::getJdbcUrl);
        registry.add("spring.datasource.username", mySqlContainer::getUsername);
        registry.add("spring.datasource.password", mySqlContainer::getPassword);
        registry.add("spring.datasource.driver-class-name", mySqlContainer::getDriverClassName);
    }

그래서 스프링에서 제공하는 동적으로 프로퍼티를 주입해주는 방법을 이용해서 컨테이너에서 url, username, password, drive-class-name을 읽어와서 주입해줍니다.

참쉽죠?

😳 다시 고민

처음에는 MySQL 환경에서 테스트를 필요로하는 테스트만 골라서 적용하려했는데요.
하다보니까 모든 테스트를 동일한 환경에서 돌려야겠다는 생각이 들어서 적용범위를 모두로 넓혔습니다.
하지만 테스트 성능이 매우매우 저하됐는데요.
제 로컬 환경이 좋지 않아서 그런것도 있겠지만 테스트 속도가 5초에서 40초 가까이 저하됐습니다.
이번 CI 파이프라인에서도 테스트 속도를 체크해보고 적용범위를 같이 고민해봐야될 것 같아요!!

⏳ 작업 시간

추정 시간: 30분
실제 시간: 2시간
이유: 어떤 방법을 선택할지와 공부를 조금 했어용

🔗 참고자료

공식 문서
공식 예시 레포
딜리셔스 기술 블록

Copy link
Contributor

github-actions bot commented Nov 20, 2023

Unit Test Results

  36 files    36 suites   33s ⏱️
204 tests 204 ✔️ 0 💤 0
210 runs  210 ✔️ 0 💤 0

Results for commit d7cd0ed.

♻️ This comment has been updated with latest results.

Copy link
Collaborator

@zillionme zillionme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쭉 썼었는데 날라가서, 하지만 궁금증은 루카와 해결했으니 저는 approve하도록 하겠습니다!
루카 수고하셨습니다!

@dooboocookie dooboocookie merged commit eb385e5 into dev_backend Dec 7, 2023
3 checks passed
@dooboocookie dooboocookie deleted the test/#582 branch December 7, 2023 02:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
D-0 🍀 백엔드 백엔드 라벨
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants