오늘은 하루종일 신규 기능 개발을 진행했다. 어제 하루종일 이력서만 수정하다, 글을 작성하고나서부터 약 두시간 정도 개발을 헸는데 진짜 너무 재밌었다. 개발이 되서 재미있는 것보다는, 개발을 하기 위해 고민하고 생각하는게 정말 재밌었다.(이력서를 출력하는데 계속 조금씩 수정하는 것과 너무 대비됬다..) 하지만 이 오늘 개발 과정이 순탄했던 것은 아니다. 테스트를 진행하며, 기대한 동작을 검증하며 기능 개발은 완료했으나 전체 테스트에서 불규칙하게 테스트가 실패하는 문제를 직면했다. 개인적으로 가장 당황스러운 경우인데 해당 문제 발생이 후, 5시간 조금 더 걸려 해당 문제를 해결했다. 의심이 가는 부분을 해결했다고 했는데 이게 생각과는 다르게 흘러가 힘들었던 것 같다.
TestContainers 사용을 통한 Redis 테스트 및 기능 개발
나는 신규 기능, 특정(한정판) 상품에 대한 계정 당 1회 주문 제한을 구현하기위해 Redis 를 활용하기로 하였다. 그리고 오늘 아침부터 본격적으로 해당 기능 개발을 진행하기 시작했다.
나는 이번 기회를 통해 Redis를 처음 사용해보았다. 그래서 Redis에 메모리 DB 라는 정도만 이해한 상태였고, 관련된 다양한 블로그 포스팅을 참고하다가 참고하기 좋은 글을 하나 발견했다. 해당 포스팅에서는 RedisService를 통해 다양한 Redis자료구조를 편리하게 사용할 수 있도록 개발하여 사용하고 있었다. 그리고 나는 해당 클래스를 통해, Redis 자료구조에 대한 이해 겸, 추후 활용에 유용하게 사용할 수 있을 것 같아 해당 코드를 참조했다.
작성된 코드의 동작을 이해하는데도 활용이 가능하고, 작성된 테스트 코드를 통해 또 해당 클래스를 이해하는데 도움이되니 테스트 코드는 정말 유용한 것 같다. 그런데 이 테스트 코드를 작성하는데 약간(?)의 고민과 시간이 들었다. 그 이유로는 테스트를 위한 Redis를 어떻게 사용할 것인가 에 대한 고민때문이다.
기본적으로, local 에는 한개의 Redis가 반드시 필요하다. 실제 애플리케이션을 실행하여 E2E 테스트를 진행해야 하기 때문이다.
그렇다면 테스트코드를 위한 방법은 어떻게 될까? 지난, MySQL 처럼 test 를 위한 애플리케이션(컨테이너)를 하나 더 띄워 테스트를 위한 컨테이너를 구축하거나, 혹은 EmbededKafka 처럼 Library 를 사용하는 방법 그리고 지금 내가 사용한 TestContainers 를 사용하는 방법이 있었다. 각 방법에는 장단점이 존재했다.
- Local 컨테이너 구축: 지금 상황에서 개인적으로 가장 쉬운 방법이나, 미래를 생각해본다면 개발 환경을 위해 구축해야하는 요소가 추가된다. 그리고 데이터 관리를 해야할 뿐만아니라 리소스도 관리해야한다.
- EmbededRedis 사용: 해당 라이브러리의 지원이 끊겼다. M1이슈도 존재하며, 다수의 불편함이 존재했다.
- TestContainers 사용: 테스트 실행 시, 별도의 프로그램 또는 스크립트가 필요없으나, 테스트 속도가 느려지는 단점이 존재한다.
그렇게 나는 고민을하다, TestContainers 를 사용해보기로 결심했다.
TestContainers
TestContainers 를 접한것은 생일선물로 받은 더 자바, "코드를 테스트 하는 다양한 방법" 강의를 통해 알게되었다. 생각보다 빨리 이를 사용해볼줄은 몰랐는데, 이번 기회를 통해 사용해보게 되었다.
TestContainers 를 사용하기 위한 세팅은 어렵지 않았다. 필요한 종속성을 추가하기만하면 끝이었다.
근데 지금 생각해보니깐, 저 상자를 기준으로 아래 종속성은 없어도 될 듯하다.(그리고 없이 테스트를진행해보니, 정상적으로 테스트가 성공한다.) 블로그 글을 참고해서 추가했는데, 한번 다시 참고하고 정리해두어야겠다.
사용하려는 컨테이너에 맞춰 모듈을 추가하거나 GenericContainer 를 사용하여, Docker Image에 따른 컨테이너를 따로 설정할 수 있다. 그리고 Redis의 경우 모듈이 제공되지 않아, GenericContainer 를 생성하여 테스트를 진행하였다.
테스트별로 컨테이너를 생성할 수도 있지만, 나는 테스트 메서드 별로 컨테이너가 생성되고 중지되고한다면 테스트가 너무 느려질것이라 생각하여, 테스트 내 컨테이너가 공유되도록 설정하였다.
이렇게 설정만하면 끝이다. 적고나니 너무 간단한데.. 이를 공부하고 알아보고 하는데는 시간이 꽤나 소요되었다. 그래도 해당 설정 이후, RedisService 를 테스트 하며 어떤 자료구조를 사용하여 개발 요구사항을 충족시킬지 감을 잡을 수 있었다.
요구사항 개발: ProductAppliedRedisRepository
먼저, 나는 해당 기능을 위한 Redis 저장소를 어느 도메인에 추가해야하는지를 고민해보았다. 개발 이전에는 "주문 제한이니깐, Order 도메인에 추가해야지" 하였으나 이는 잘못된 생각이었음을 알 수 있었다. 한정판이라는 것은 상품(Product) 이면서 해당 상품을 주문한 유저(Member)가 제한이 되어야하는 것이었다.
그렇게 나는 Product 도메인의 Out.DataAccess.repository 내에 해당 인터페이스와 구현체를 추가했다.
테스트 코드가 길어 전체를 캡쳐하지는 못했지만, 나는 정상과 에러상황을 검증했다. 그런데 지금생각해보니, 동시성 테스트가 추가되는 것이 좀더 해당 요구사항을 검증하는데 도움이 될 듯하다. 이는 내일 추가해야겠다.
여기까지는 첫 RedisService 테스트 이후로는 금방 할 수 있었다. 그러나 다른 문제가 나를 오랫동안 괴롭혔다.
수정없이 간헐적으로 성공하는 ProductAppliedRedisRepositoryImplTest
언제나 그렇듯, 모든 개발 이후 전체 테스트를 실행하고 나는 커밋 및 푸시를 할 준비를 하고있었다. 그런데 왠걸? 정상 성공하던 ProductAppliedRedisRepositoryImplTest 가 실패하는 것이아닌가? 나는 다시 해당 테스트를 실행해 보았지만, 정상적으로 성공했다. 그리고 다시 전체 테스트를 가동해본 결과 성공했다. 그렇게 나는 이게 내 로컬 이상한 캐시가 남아있어 문제가 되는것은아닌가? 하는 의구심이 들었지만, 일단 Push 를 해보았다. 하지만 CI는 실패했다.
아차, CI 를 수정하는것을 깜빡했다. 앞서 말하지 명확하게 언급하지 않은 부분이 있는데 TestContainers 는 Docker 를 사용한다. 테스트가 실행될 때, Docker 컨테이너를 띄워 테스트를 진행하기에 빌드 및 테스트 과정이 포함된 CI 를 진행하기 위해서는 Docker 를 설치하는 것이 필요했다.
어렵지 않게 해당 명령어를 추가했으나, 처음에는 containerd.io의 충돌이 발생해 에러가 발생하였고, GPT를 통해 해당 문제를 사진과 같이 해결하였다.
하지만 진짜 문제가 발생했다. 정상 성공하던 ProductAppliedRedisRepositoryImplTest 가 다시 실패했다. 나는 다시 로컬에서 테스트를 실행했다. 그리고 테스트는 보란듯이 실패했다. 그리고 다시 실행했다. 그리고 테스트는 날 놀리듯 성공했다.
올것이 왔구나 하는 생각이 들었다. 쉽게쉽게 갈리가 없지 ~ 하며 나는 에러 로그를 보며 문제의 원인을 파악해보기 시작했다.
개별테스트에서는 아무리 반복하여 실행해도 에러는 발생하지 않았다. 그리고 어떤 코드도 수정하지 않고 전체테스트를 다시 돌리면 실패하거나 혹은 성공했다. 와 진짜 신기했다. 답답하면서도 신기했다. 어떤 설정때문에 이것이 문제가 된걸까? 진짜 한참을 찾아보다 관련된 에러 사례를 확인하지 못하고, 다시 로그를 통해 문제 해결 힌트를 얻을 수 있었다.
나는 다시 로그를 천천히 확인하면서 그 문제의 원인을 알 수 있었다. 전체 테스트가 가동되고 테스트가 실패할 때는 해당 테스트가 TestContainers 를 바라보는 것이아닌, 내 application.yml 에 작성된 값을 참조하는 것이었다. TestContainers 는 랜덤 포트로 컨테이너가 생성되기에, 해당 주소를 바라보면 안된다.
하지만 이를 어떻게 해결해야할지 감을 잡지 못했다. "어떻게 하면 application.yml을 바라보는 것을 어떻게하면 안바라보게 할 수 있을까?" 를 고민하다, application.yml의 설정 값을 지웠는데 이젠 엉뚱한 주소를 바라보았다.
아... 진짜 어떻게하면 좋을까.. 하다, 강의의 내용이 생각이 났다. "Testcontainers, 컨테이너 정보를 스프링 테스트에서 참조하기" 의 내용이 생각이 났고, 해당 내용을 참고해보았다.
해당 방법을 요약하여 설명하면, TestContainers 를 통해 컨테이너를 생성(랜덤포트)하고 해당 컨테이너의 정보를 추출하여, Environment 에 넣어 Spring의 프로퍼티를 동적으로 수정한다. 이를 위해 @ContextConfiguration 을 사용한다.
그리고 이를 통해 정상적으로 테스트가 성공하는 것을 볼 수 있었고, CI 또한 초록불을 볼 수 있었다.
오늘은 쉬우면서도 쉽지 않은 하루였지만 너무 재밌었다. 이력서에 고통받다가 하루종일 개발하니 더 즐거웠던 것 같다. 근데 이력서를 빨리 지원하고서 이 개발도 재미나게해야하는데... PDF로 보기좋게 분할하는게 참 어렵다... 내일은 오늘 기능 개발내용을 Order 도메인의 유스케이스에 추가하여 신규 기능 개발을 완료하는 것을 목표로 해야겠다. 그리고 이력서도.... PDF 잘 자르기만하면되니깐.... 이것까지 하는 것을 목표로하며...
아좌좌 할수있다~~~~~!!
'회고 > TIL' 카테고리의 다른 글
이력서 지원 시작, 독서: 소프트웨어 가치와 비용, 기존 이슈 작업 시작 및 신규 이슈 (0) | 2023.09.13 |
---|---|
특정(한정판) 상품에 대한 계정 당 1회 주문 제한 기능추가 마무리, 인프라 구축, yml 분리의 필요성 (0) | 2023.09.11 |
독서: 육각형 개발자, 하루종일 이력서 수정.. 또 수정.. (0) | 2023.09.06 |
사용 DB 변경과 JPA, 독서 (0) | 2023.09.04 |
생일, 책 선물 (0) | 2023.09.03 |