디버로 이직 후 처음으로 진행한 작업에 대해 회고한다. 작업의 구체적인 내용보다는 테스트 코드가 없던 개발 환경에서 테스트 코드를 작성할 수 있는 환경 구성, DB에서 값을 가져오는 메서드의 모킹에 대한 고민 그리고 실무 경험을 통해 깨닫게 된 테스트 코드의 장점에 대해서 공유한다.
개발자로서는 두 번째 회사, 디버에서 디포스트라는 서비스의 백엔드 개발을 하게 되었다. 첫 회사에서는 내가 처음부터 서비스 종료까지 개발 및 유지보수를 담당했었다. 하지만 당연하게도 디버에서는 내가 작성한 코드가 한 줄도 없었다.
이러한 상황에서 기존에 계시던 개발자 분은 인수인계 이후에 퇴사를 하셨다. 코드에서 문제가 발생하고 해결해야할 사람이 나밖에 없다는 압박감이 많이 들었다 (물론, 히스토리를 알고 계시는 든든한 팀장님이 있었다). 전임자가 퇴사하기 전에 관리되고 있던 API문서들과 데이터베이스 관련 문서들의 최신화를 전임자에게 요청드렸다.
그런 와중에 첫 작업이 할당되었다. 서비스 중인 디포스트의 6개의 주문 접수 API의 코드를 수정해야하고, 관련 기능 몇 개를 수정 / 추가하는 작업이었다. 새로 오픈하게 되는 지점에서 요청한 기능이고, 오픈 일정은 정해져있었기 때문에 일정을 무한정 넉넉하게 요청할 수도 없었다.
일단 해야했다.
서비스 자체는 문제 없이 돌아가고 있었고, API의 코드도 정상적으로 잘 동작했다. 하지만 주문 접수 API 코드들을 파악하기가 매우 어려웠다.
아래에 나열한 5가지 정도의 어려움이 있었다.
이 때, 선택에 기로에 섰다.
테스트코드 환경을 구성하는게 보편적으로는 옳은 선택이라고 생각한다. 그렇지만, 마감 일자가 정해진 일정 내에서 후자를 선택하여 마감 일자를 지키지 못할 것만 같은 두려움이 있었다. 하지만, "나중은 결코 오지 않는다"는 르블랑의 법칙을 상기시키며 테스트코드 환경을 구성하기로 마음을 먹었다.
이전 개발자들은 도커와 도커 컴포즈를 활용하여 로컬 개발환경을 세팅해서 개발해왔다고 한다. 개발환경에는 인증/인가 서버, 레디스, 데이터베이스 등이 있었다. 어떤 이유에서인지 모르지만 도커 컴포즈로 로컬 개발환경을 실행하고 jest를 실행해서 피드백까지 10초 내지는 20초의 시간이 걸렸다. 작은 코드를 수정하고 테스트 코드 실행 후 피드백을 받기에는 너무 많은 시간이 걸렸다.
일단 인증/인가 서버, 레디스 서버 등 주요한 기능이 담기지 않은 서버들은 개발 환경에서 제외시켰다. 필요하다면 해당 서버들에 요청을 하는 모듈/서비스들은 모킹 처리할 생각이었다. 데이터베이스를 위한 도커 컨테이너만 구성하고 주요 기능을 담당하고 있는 API 서버는 인텔리제이 IDE를 이용해 테스트 코드를 실행해가면서 개발할 수 있는 환경을 구성했다.
IDE를 이용해 로컬 개발 경을 구성했을 때는 IDE 내장 디버거를 적극적으로 사용할 수 있었고, 수초 이내의 빠른 테스트 코드 피드백으로 조금 더 쾌적한 환경이 구성되었다. 현재는 CI/CD 단계에서는 테스트 코드를 실행하고 있지 않다. 내가 구성한 세팅은 CI/CD를 고려한 세팅이 아니라 한계가 분명하지만, 일단 테스트 코드를 이용해 개발할 수 있어서 의미가 있었다.
기존 로컬 개발환경
테스트 코드 작성을 위해 변경한 로컬 개발 환경
기능을 처음부터 만든다면 작은 기능들을 만들면서 NestJs의 서비스 프로바이더들에 대한 유닛 테스트를 작성했을 것 같다. 하지만 내가 해야할 일은 이미 작성된 API 코드 내에서 추가적인 작업을 하는 것이었다. 내가 작성한 코드가 기존의 잘 동작하는 코드에 영향을 끼치는지 알려줄 테스트 코드가 필요했다. 따라서, 유닛 테스트 보다는 e2e 테스트를 먼저 작성해가면서, 개발을 진행했다. 테스트 코드를 작성하면서 고민했던 것은 데이터베이스에서 값을 가져오는 부분을 모킹할 것인가와 내가 잘 모르는 API에 대해 input과 output을 설정하고 테스트 픽스처를 만드는 일이었다.
사이드 프로젝트를 진행할 때는 DB에서 데이터를 가져오는 코드들에 대해서 모킹을 많이 했다. 테스트 코드에서 실제 데이터베이스에 요청을 하지 않아도, 서로 얽힌 다른 메서들 간에 어떠한 값을 리턴하는지 내가 알고 있었기 때문에 모킹이 가능했고, 테스트 코드가 더 빨리 실행된다는 이점이 있었다.
하지만 이번에는 DB에서 데이터를 가져오는 코드들에 대해서 모킹을 하지 않았다. 그 이유는 해당 코드들이 어떠한 값을 리턴할 지 예측할 수 없었고, 모킹을 하려면 모킹 대상인 메서드들이 너무 많았다. 그리고 아직 많은 테스트 코드들이 존재하지 않았으므로, 테스트 코드 실행속도에도 영향이 없었다. DB에 접근하는 메서드들을 모킹하지 않음으로서 이 메서드들이 라이브 환경에서 어떻게 동작할지 예측이 가능해졌다.
단, 불필요하게 외부 서버를 호출하는 메서드나 모듈들은 모킹 처리를 했다. 예를 들어, 슬랙에 메시지를 보내는 모듈, Nest에서 제공하는 HttpService 등이 있다.
개발 환경은 구성했지만 내가 손봐야할 코드들은 간단한 코드들이 아니었다. 우선 테스트 픽스쳐를 만드는 것부터가 어려웠다. API의 요청 body에 어떠한 값을 전달해야하는지도 파악이 쉽지 않았다.
그래서 잘 돌아가고 있는 개발 환경에서 브라우저로 직접 주문을 넣어보면서 해피 케이스들에 대한 주문 API의 응답, 요청 바디들을 추출했다.
테스트 코드에 입력값과 결과값들을 세팅해놓고, 내가 추가하는 로직들이 기존 코드에 영향을 주지 않는지를 파악하면서 개발을 진행했다.
테스트 코드를 실무에서 작성하고 적용해본 것은 처음이다. 아직 테스트 코드에 대한 많은 경험이 있지만, 이번 계기를 통해서 다음과 같은 장점을 알게 되었다.
테스트 코드를 잘 작성하는 방법에 대한 고민을 많이 하게 되었다. 또한 테스트 코드도 코드이기에 유지보수를 위해서는 리소스가 소요된다. 하지만 테스트 코드가 발생시키는 리소스 소모나 단점 보다는 얻게 되는 장점이 많은 것 같다.
어떤 방법으로 테스트 코드를 잘 작성하고, 유지보수할 수 있는지는 조금씩 더 고민을 해봐야할 것 같다.
테스트 코드와 함께 개발하면서 복잡한 로직의 실제 구성을 파악할 수 있었습니다. 이전에는 모킹을 적극적으로 활용했었는데, 모킹을 항상 활용할 수 있는 것은 아니며 필요에 따라서는 모킹보다 실제 데이터베이스에서 값을 가지고 오는 것이 더 효율적이고 안전한 방법이라는 것을 알게 되었습니다. 마지막으로 테스트 코드와 함께 개발해나가면서 코드 파악에 대한 자신감이 생기고, 요구 사항이 변경되었을 때 이를 수용할 수 있는 마음의 여유가 더 생기기도 했습니다.
감사합니다.