Skip to content

Feat/Test Container #364

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

Merged
merged 16 commits into from
May 19, 2025
Merged

Feat/Test Container #364

merged 16 commits into from
May 19, 2025

Conversation

ChungPlusPlus
Copy link
Contributor

@ChungPlusPlus ChungPlusPlus commented May 7, 2025

Test Container 추가

한줄 요약

테스트 컨테이너 적용했습니다

상세 설명

testcontainer는 테스트 실행시 컨테이너를 도커로 자동으로 띄워줘 사용할 수 있게 하는 라이브러리입니다
외부 의존성 없이 실제 prod과 유사한 환경에서 테스트 진행이 가능합니다

TestContainerInitializer를 사용해서 테스트가 실행 될 때 환경변수를 바꿔주었습니다

@ContextConfiguration(initializers = [TestContainerInitializer::class])
어노테이션을 적어준다면 MySQL을 이용해 테스트가 진행됩니다

ServiceConnection 어노테이션을 사용하면 Spring이 자동으로 TestContainer로 띄워진 컨테이너에 연결이 된다고 합니다

@Import(MySQLTestContainerConfig::class)

앞으로 모든 테스트 클래스마다 위 어노테이션을 사용해야 합니다

image

[9d3e478]: 테스트 환경에서 getLoginUser()가 2번 이상 실행되면 데이터베이스에 같은 이름의 사람을 2번 이상 저장하게 되어 Unique 때문에 오류가 발생해서 고쳤습니다.

TODO

테스트컨테이너 인스턴스를 하나 만들어서 공유하는 방식이라 한 테스트에서 사용했던 데이터가 남아있다는 문제가 있습니다.
예를 들어서, 공지를 추가하고 공지가 1개 있음을 확인하는 테스트가 2개 있다고 해보겠습니다. 현재는 테스트 하나가 끝나도 데이터베이스에는 데이터가 계속 남아있어 다음 테스트에는 공지가 1개 더 추가되어 2개가 생겨버립니다.
이렇게 테스트 코드끼리 서로 간섭할 수 있는 부분을 해결해야 합니다.

reservationService에 동시성 문제가 조금 있는 것 같아 일단 테스트에 주석 처리해뒀습니다

@ChungPlusPlus ChungPlusPlus requested review from leeeryboy and huGgW May 7, 2025 14:33
@ChungPlusPlus ChungPlusPlus self-assigned this May 7, 2025
Copy link

github-actions bot commented May 7, 2025

Test Results

16 files  16 suites   1s ⏱️
63 tests 63 ✔️ 0 💤 0
67 runs  67 ✔️ 0 💤 0

Results for commit 1f0f499.

♻️ This comment has been updated with latest results.

@huGgW
Copy link
Member

huGgW commented May 10, 2025

테스트컨테이너 인스턴스를 하나 만들어서 공유하는 방식이라 한 테스트에서 사용했던 데이터가 남아있다는 문제가 있습니다.

이 부분은 SpringTestLifeCycle 설정을 통해서 해결될 수 있는 것으로 알고 있습니다..! (확실하진 않음) (다른 서비스 레이어 테스트 코드 참고해주세요..!)

@SpringBootTest
@ActiveProfiles("test")
@ContextConfiguration(initializers = [TestContainerInitializer::class])
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
Copy link
Contributor

@leeeryboy leeeryboy May 11, 2025

Choose a reason for hiding this comment

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

TestInstance 어노테이션은 JUnit꺼라 lifecycle이 제대로 적용될지 모르겠네요

추가로 테스트 격리성에 문제가 있으면 kotest 공식 문서랑 아래 블로그 글들 참고해보시면 좋을거 같아요
https://kth990303.tistory.com/374
https://velog.io/@glencode/Kotest%EC%9D%98-Lifecycle-Hook-IsolationMode
testcontainer와 호환이 잘 되는지는 아마 테스트 해봐야할것 같구요

@ChungPlusPlus
Copy link
Contributor Author

ChungPlusPlus commented May 13, 2025

Transactional 어노테이션과 extensions(SpringTestExtension(SpringTestLifecycleMode.Root))를 사용해 문제를 해결하려 했으나, 생성한 엔티티가 아예 조회가 안되는 문제가 생겼습니다
이유를 알아보니: 테스트에서 생기는 트랜잭션 속에서 생성한 데이터는 commit되기 전까지 다른 트랜잭션에선 조회되지 않는다. 테스트가 돌아가는 중에 totalSearch가 호출되면 totalSearch는 새로운 트랜잭션에서 이루어진다. 데이터를 생성하는 트랜잭션이 끝나기 전에 다른 트랜잭션에서 totalSearch로 조회를 해보니 조회가 안 된다.

이 문제를 해결하려면 테스트에 트랜잭션을 쓰지 말고, 대신 afterContainer를 이용해 테스트가 끝나고 데이터베이스를 초기화해줘야 한다 생각했습니다.
해당 테스트 코드에서 어느 레퍼지토리를 수정했는지는 알기 힘들기 때문에 CleanUp을 구성해서 전부 truncate를 해주는 방식으로 만들어봤습니다

Comment on lines 18 to 19
return camelCase.replace(Regex("([a-z])([A-Z])"), "$1_$2")
.lowercase(Locale.getDefault())
Copy link
Member

Choose a reason for hiding this comment

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

(사실 저희 케이스 정도는 만족해서 마이너 코멘트입니다.)

엣지케이스가 있는 구현인거 같아요.

  1. https://www.baeldung.com/java-camel-snake-case-conversion 에 나온 방법으로 보완하던가
  2. https://github.com/google/guava/wiki/StringsExplained#caseformat 라이브러리 사용이 필요해 보입니다.

(엣지케이스 커버하려면 라이브러리가 나을거 같긴 한데 두 방법 다 괜찮아 보여요)

@huGgW
Copy link
Member

huGgW commented May 14, 2025

Transactional 어노테이션과 extensions(SpringTestExtension(SpringTestLifecycleMode.Root))를 사용해 문제를 해결하려 했으나, 생성한 엔티티가 아예 조회가 안되는 문제가 생겼습니다 이유를 알아보니: 테스트에서 생기는 트랜잭션 속에서 생성한 데이터는 commit되기 전까지 다른 트랜잭션에선 조회되지 않는다. 테스트가 돌아가는 중에 totalSearch가 호출되면 totalSearch는 새로운 트랜잭션에서 이루어진다. 데이터를 생성하는 트랜잭션이 끝나기 전에 다른 트랜잭션에서 totalSearch로 조회를 해보니 조회가 안 된다.

이 문제를 해결하려면 테스트에 트랜잭션을 쓰지 말고, 대신 afterContainer를 이용해 테스트가 끝나고 데이터베이스를 초기화해줘야 한다 생각했습니다. 해당 테스트 코드에서 어느 레퍼지토리를 수정했는지는 알기 힘들기 때문에 CleanUp을 구성해서 전부 truncate를 해주는 방식으로 만들어봤습니다

테스트 코드 봤을 때는 내부에서 별도의 transaction을 생성하는 코드는 없는 것으로 보이는데, 어느 부분에서 다른 transaction이 생성된다는 것일까요?
transactional strategy가 별도로 설정된 것도 아니고 async event listener도 아니라서 기존 트렌젝션을 그대로 사용하는 것으로 기억해서요..!

@ChungPlusPlus
Copy link
Contributor Author

ChungPlusPlus commented May 15, 2025

Transactional 어노테이션과 extensions(SpringTestExtension(SpringTestLifecycleMode.Root))를 사용해 문제를 해결하려 했으나, 생성한 엔티티가 아예 조회가 안되는 문제가 생겼습니다 이유를 알아보니: 테스트에서 생기는 트랜잭션 속에서 생성한 데이터는 commit되기 전까지 다른 트랜잭션에선 조회되지 않는다. 테스트가 돌아가는 중에 totalSearch가 호출되면 totalSearch는 새로운 트랜잭션에서 이루어진다. 데이터를 생성하는 트랜잭션이 끝나기 전에 다른 트랜잭션에서 totalSearch로 조회를 해보니 조회가 안 된다.
이 문제를 해결하려면 테스트에 트랜잭션을 쓰지 말고, 대신 afterContainer를 이용해 테스트가 끝나고 데이터베이스를 초기화해줘야 한다 생각했습니다. 해당 테스트 코드에서 어느 레퍼지토리를 수정했는지는 알기 힘들기 때문에 CleanUp을 구성해서 전부 truncate를 해주는 방식으로 만들어봤습니다

테스트 코드 봤을 때는 내부에서 별도의 transaction을 생성하는 코드는 없는 것으로 보이는데, 어느 부분에서 다른 transaction이 생성된다는 것일까요? transactional strategy가 별도로 설정된 것도 아니고 async event listener도 아니라서 기존 트렌젝션을 그대로 사용하는 것으로 기억해서요..!

트랜잭션이 새로 생기면 조회가 안 되는 게 설명이 되어서 그렇게 생각했는데, 제가 잘못 생각한 것 같습니다
다시 찾아보니 FULL TEXT INDEX가 트랜잭션이 커밋이 되어야지 생겨서 조회가 안됩니다

@ChungPlusPlus
Copy link
Contributor Author

ServiceConnection 어노테이션을 사용하면 Spring이 자동으로 TestContainer로 띄워진 컨테이너에 연결이 된다고 합니다

@Import(MySQLTestContainerConfig::class)

앞으로 모든 테스트 클래스마다 위 어노테이션을 사용해야 합니다

TODO : reservationService에 동시성 문제가 조금 있는 것 같아 일단 테스트에 주석 처리해뒀습니다

Copy link
Contributor

@leeeryboy leeeryboy left a comment

Choose a reason for hiding this comment

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

getLoginUser()에서 동시에 유저 생성할때 이슈가 되어서 해당 함수에서는 test user를 찾도록 하고 각 테스트 내에서 loginUser가 필요한 경우 beforeSpec에서 유저를 미리 생성해서 테스트 진행하도록 리팩토링하였습니다. 필요하시면 [1c70642] 확인하시면 됩니다.

@ChungPlusPlus ChungPlusPlus merged commit efccd3e into develop May 19, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants