현재 회사에서 일을 하다보니 항상 힘든 부분들이 있었어요. 대표적으로 자체 테스트였어요. 프론트엔드 개발자는 유난히 다른 팀원들에 비해 테스트에 더 많은 시간을 할애하고 있었어요. 자체 테스트를 아무리 꼼꼼하게 한다고 해도 버그는 항상 발생했고 주기적으로 진행하는 핫픽스 횟수도 줄지 않았어요.
이러한 상황에서 왜 우리 조직은 테스트코드를 작성하지 않을까? 에 대한 의문이 생기기 시작했어요. 회사에 오래 재직중인 몇몇 팀원을 찾아가 의견을 여쭤봤어요.
이러한 상황에서 세번째 문제는 천천히 해결할 수 있는 문제라 생각했어요. 하지만 왜 작성할 시간이 없고 작성할수 없는지에 대해서 궁금점이 생기기 시작했어요. 그렇게 ‘왜 우리 조직에선 테스트코드를 작성할 수 없을까?’ 와 꾸준히 발생하는 안정성 문제를 해결하기 위해 이 작업을 시작하게 되었어요.
제가 재직중인 회사는 어떻게보면 초기 시절에 J 커브를 그린 회사이기도 해요. 그러다보니 빠른 기능 개발과 변경점이 발생하기도 했었을거에요. 이런 상황에서 테스트 코드를 작성할 시간이 없었다고 느껴지기도 했을 것 같아요.
하지만 저는 꾸준히 개발을 진행하면서 테스트 코드를 작성하지 않을시에 이러한 문제가 있을거라 판단했어요.
코드에 변경점이 발생했을때 어떠한 부분에서 문제가 발생할지 예측하기 어려웠어요.
이러한 상황이 꾸준히 발생하면서 디버깅과 테스트를 진행하다보니 결국 테스트 코드를 작성하는 시간보다 반복적인 업무에 할애하는 시간이 더욱 길거같다는 생각을 했어요. 그리고 사실상 지금은 AI 를 통해서 빠르게 테스트코드를 작성할 수 있게 되었어요.
저희 서비스는 서비스 특성상 결제 기기를 다루고 있어요. 이러한 상황에서 가장 중요한 기능은 대부분 주문, 결제와 관련된 기능이기도 해요. 그러다보니 이렇게 민감한 기능을 테스트하지 않는다면 다른 테스트들은 모두 의미가 없다 라고 주장하는 팀원들도 존재했어요. 하지만 생각보다 결제, 주문 관련 기능보다 다른 기능에서 오는 문제들에 대한 유저보이스가 훨씬 많았어요.
코어 서비스는 어떻게보면 레거시 코드로 이뤄지기도 했어요. 이러한 상황에서 테스트 코드를 작성하게 된다면 마이그레이션으로 인한 변경점을 대응하거나, 더 어렵게 테스트코드를 작성해야 한다는 걱정이 있었던 것 같았어요.
하지만 이런 습관이 쌓여 결국 최신 서비스에서도 최신 기술을 사용하는 반면 테스트 코드는 단 한줄도 없게 되었어요. 단순 함수도 테스트코드를 작성하지 않으니 잔버그가 점점 늘어나게 되었어요.
이러한 상황에서 계속 현실을 외면하는 것 보단 성공하던 실패하던 왜 테스트 코드를 작성할 수 없는 환경인지 더욱 집요하게 파악해봐야 한다고 생각하게 되었어요.
왜 우리는 테스트 자동화 환경을 적용할 수 없을까? 에 대해서 논의하기 위한 테스트 자동화 스터디를 진행해보았어요. 아무래도 다들 업무를 진행하면서 스터디에 참여해주셨기 때문에 더욱 고민해가면서 진행했던 것 같아요. 그러다보니 아래의 과정으로 스터디를 진행해보았어요.
이렇게 진행해보니 자연스럽게 스터디보다는 흥미로운 대화를 진행한다는 느낌으로 진행하게 되었고, 다행스럽게도 참여율이 다른 스터디에 비해 높았어요.
여러 흥미로운 의견이 있었어요. 테스트에 대해서 레퍼런스를 찾아봤을 때 가장 먼저 등장하는것이 바로 테스트 피라미드 였어요. 이 모델은 단위 → 통합 → E2E 순으로 테스트를 많이 작성해야 한다고 하지만 팀원들의 의견은 달랐어요. 우리는 E2E → 단위 → 통합 순으로 테스트 코드를 작성해야 한다는 의견이 있었어요. 이유는 아래와 같았어요.
API 의 실제 응답 결과에 의존하는 기능이 많았어요.
제가 담당하던 서비스에는 알림톡 발송 기능이 있었어요. 이러한 기능이 정상적으로 동작하는지 확인하기 위해선 E2E 테스트를 통해 실제로 API 호출에 성공했는지 확인해야 했어요. 만약 모킹을 통해 진행했다면 개발자는 해피케이스만 테스트하게 되고, 운영환경에서 큰 오류가 발생할 수도 있어요. 이런 기능이 적지 않다보니 실제 환경에서의 높은 안정성을 유지하기 위해선 E2E 테스트 케이스가 더 많아야한다는 의견이 있었어요.
통합 테스트를 진행하기엔 모킹을 해야하는 API 가 너무 많고 상황이 복잡했어요.
사내 서비스는 빠르게 개발되어서 많은 컴포넌트의 구조가 테스트를 진행하기에 적합하지 않았어요. 아주 작은 단위의 컴포넌트만 테스트를 하고 싶다고 해도 의존하는 로직이 많아 어떤 케이스에선 최대 10개의 API를 호출해야하기도 했어요. 하지만 E2E 테스트는 실제 DB 와 환경에 의존하는 대신 모킹을 해야하는 상황이 많지 않았어요. 이러한 상황에서 더욱 현실적으로 생각해보기로 했어요.
여러 레퍼런스를 참고해보고 직접 테스트 관련 서적의 서평단에 참여해보기도 했어요. 그리고 테스트를 실행하기 쉬운 환경이 무엇인지 고민해보고 최대한 그에 맞는 코드 구조를 적용하려고 노력해봤어요. 이 상황에서 놀랐던건 테스트 코드를 작성하기 쉬운 코드 구조로 작성하다보니 자연스럽게 코드 가독성이 좋아지고 변경에 용이한 인터페이스를 갖게 되었어요.
테스트 라이브러리의 경우에는 선택지가 Jest
, Vitest
2가지였어요. 코어 서비스는 이미 아주 적은 테스트케이스가 Jest 로 작성되어있었기 때문에 고민을 하게 되었어요. 하지만 테스트를 진행하는 서비스의 환경에 맞는 프레임워크를 사용하기 위해 아래의 이유로 Vitest
를 선택하게 되었어요.
Jest
의 경우 모킹이 제한적이었어요.
코어 서비스는 Redux
를 사용하고 있었고 무수히 많은 셀렉터가 정의되어있었어요. 특정 컴포넌트의 테스트를 위해선 셀렉터의 모킹이 필수적이었어요. 이러한 상황에서 Jest
는 테스트가 실행되는 테스트 파일 내에서만 모킹을 할 수 있었어요. 하지만 Vitest
는 마치 모킹용 셀렉터를 따로 정의하듯이 다른 파일에 미리 정의해둘 수 있어 셀렉터의 변경점이 발생했을때 모킹 코드를 각 파일마다 일일히 수정할 필요가 없었어요.
모두가 아는 사실이지만
Vitest
의 테스트 실행 속도가 굉장히 빨랐어요.
통합 테스트의 경우에는 단순 자바스크립트만으로 실행되지 않기 때문에 단위 테스트보다 실행 속도가 느릴 수 밖에 없었어요. 컴포넌트 테스트가 CI 환경에서 실행될 때 Jest
의 경우에는 Vitest
보다 최소 2배 이상 느렸어요. 팀원에게 테스트 코드 작성을 독려하기엔 기존 환경보다 효율이 떨어지는 부분이 생기면 안된다고 생각해서 Vitest
를 선택하게 되었어요.
그리고 변경점이 많은 서비스에 최대한 대응하기 쉽도록 Mock Service Worker 를 통한 모킹 로직 또한 추상화를 통해 재사용할 수 있도록 랩핑 모듈을 구현해봤어요. 이를 통해 리덕스 사가를 통해 서버 데이터를 관리하는 환경에서 제대로 동작하는 테스트 코드를 작성할 수 있었어요.
E2E 테스트의 경우에는 Playwright
를 채택하게 되었어요. Playwright
는 react-testing-library
와 문법이 굉장히 비슷하게 느껴졌어요. 이를 통해 일관된 문법으로 테스트 코드 작성을 할 수 있게 되었어요. 그리고 E2E 테스트 특성상 하나의 큰 기능을 테스트하는 경우가 많기 때문에 테스트코드 또한 작은 단위의 여러 스텝을 실행해서 큰 기능을 완성할 수 있도록 레퍼런스를 생성했어요. 이로인해 E2E 테스트 코드 또한 기능이 변경되어도 작은 스텝을 의미하는 코드 하나만 수정하면 대응할 수 있었어요.
근본적인 이유를 알 수 있게 되었어요.
테스트를 왜 작성할 수 없다고 판단하는지 직접 경험해보고 이해할 수 있게 되었어요. 사실 테스트 코드를 복잡하게 작성하는것보단 테스트 코드를 작성하기 좋은 환경을 조성하는게 더욱 좋은 방법이기도 해요. 따라서 자연스럽게 코드를 작성할때도 여러 상황을 고려하면서 작성할 수 있게 됐어요.
특이한 방식으로 스터디를 진행해봄으로써 사내 개발 커뮤니티에 기여할 수 있게 되었어요.
보통은 스터디라 하면 배운점을 공유하는 자리처럼 느껴지기도 해요. 하지만 업무를 진행하면서 스터디에 참여하기엔 이러한 상황이 부담스러울 수 있어요. 실제로 편하게 대화를 진행하는듯이 진행한 이후에 이러한 대화를 할 수 있는 기회가 많았으면 좋겠다는 긍정적인 평가를 들을 수 있었어요.
사내에 존재하지 않던 테스트 코드 레퍼런스를 남길 수 있게 되었어요.
외부에 많이 존재하지 않는 레퍼런스가 사내에 있다는건 굉장히 큰 가치를 갖게 된다고 생각해요. 테스트 코드를 작성하지 않던 상황에서 레퍼런스를 남김으로써 추후에 서비스의 안정화가 진행될 때의 러닝 코스트를 조금이라도 더 줄일 수 있게 됐어요.
테스트 커버리지가 ‘생겼어요.’
통합 / E2E 는 높은 커버리지를 달성하진 못했지만, 단위 테스트에 대해서 테스트 커버리지 90퍼센트 이상을 달성하게 되었어요. 그리고 이러한 커버리지를 의미있게 유지하기 위해 최대한 모듈을 분리하며 어느 모듈에 크게 의존하지 않는 코드를 작성하려고 노력하게 되었어요. 통합 테스트 케이스도 쉽게 작성할 수 있는 환경에 조금씩 다가갈 수 있게 되었어요.
3달동안 진행했던 작업에 비해 큰 성과를 얻지 못했다고 생각해요. 동료 개발자들이 열심히 참여하긴 했지만 아직도 테스트 코드를 어려워하는 동료들이 대부분이에요. 그리고 저 마저도 앱개발에 참여하게 되면서 애니메이션, 네이티브 모듈 등 복잡한 기능이 추가되면서 테스트 작성을 게을리 하게 되었어요.
하지만 이러한 경험이 있었기 때문에 다시 시작하는데 시간이 오래 걸리지 않게 되었어요. 실제로 현재도 앱 개발에 적응이 되면서 다시 테스트 환경을 구축하고 있어요. 이미 경험한 덕에 동일한 테스트 케이스를 작성하는데에도 이전보다 더욱 다양한 방법으로 테스트 환경을 구축하려고 하고 있어요.
팀원들이 필요해 의해 테스트 코드를 작성해야하는 상황이 온다면 제 레퍼런스가 굉장히 큰 도움을 줄것이라고 확신하고 있어요. 😁
사실 이 작업을 '테스트 코드를 작성한다면 불필요한 QA 리소스를 낭비하지 않게 될것이다.' 라는 가설을 세우고 시작하기도 했어요. 하지만 결국 돌이켜보니 테스트코드는 QA 자동화 역할을 갖고있기 보단 미처 발견하지 못했던 사이드이펙트를 미리 발견함으로써 추후에 발생할 오류를 방지하기 위한 역할이라고 느꼈어요. 실제로 잘 작동하는줄 알았던 코드에 생각치못했던 버그가 존재하기도 했어요.
여러분의 조직에서는 테스트 코드, 어떻게 도입하고 계신가요?
비슷한 고민이나 다른 경험이 있다면 댓글로 공유해 주세요! 😁